diff --git a/ceilometer/alarm/notifier/__init__.py b/ceilometer/alarm/notifier/__init__.py index 9876abc3b..0e34669a3 100644 --- a/ceilometer/alarm/notifier/__init__.py +++ b/ceilometer/alarm/notifier/__init__.py @@ -23,13 +23,14 @@ class AlarmNotifier(object): """Base class for alarm notifier plugins.""" @abc.abstractmethod - def notify(self, action, alarm_id, alarm_name, previous, current, - reason, reason_data): + def notify(self, action, alarm_id, alarm_name, severity, previous, + current, reason, reason_data): """Notify that an alarm has been triggered. :param action: The action that is being attended, as a parsed URL. :param alarm_id: The triggered alarm. :param alarm_name: The name of triggered alarm. + :param severity: The level of triggered alarm :param previous: The previous state of the alarm. :param current: The current state of the alarm. :param reason: The reason the alarm changed its state. diff --git a/ceilometer/alarm/notifier/log.py b/ceilometer/alarm/notifier/log.py index 33f4a7e95..127940783 100644 --- a/ceilometer/alarm/notifier/log.py +++ b/ceilometer/alarm/notifier/log.py @@ -25,11 +25,15 @@ class LogAlarmNotifier(notifier.AlarmNotifier): "Log alarm notifier.""" @staticmethod - def notify(action, alarm_id, alarm_name, previous, current, reason, - reason_data): + def notify(action, alarm_id, alarm_name, severity, previous, current, + reason, reason_data): LOG.info(_( - "Notifying alarm %(alarm_name)s %(alarm_id)s from %(previous)s " - "to %(current)s with action %(action)s because " - "%(reason)s.") % ({'alarm_name': alarm_name, 'alarm_id': alarm_id, - 'previous': previous, 'current': current, - 'action': action, 'reason': reason})) + "Notifying alarm %(alarm_name)s %(alarm_id)s of %(severity)s " + "priority from %(previous)s to %(current)s with action %(action)s" + " because %(reason)s.") % ({'alarm_name': alarm_name, + 'alarm_id': alarm_id, + 'severity': severity, + 'previous': previous, + 'current': current, + 'action': action, + 'reason': reason})) diff --git a/ceilometer/alarm/notifier/rest.py b/ceilometer/alarm/notifier/rest.py index 396f47c04..1312be3c7 100644 --- a/ceilometer/alarm/notifier/rest.py +++ b/ceilometer/alarm/notifier/rest.py @@ -55,8 +55,8 @@ class RestAlarmNotifier(notifier.AlarmNotifier): """Rest alarm notifier.""" @staticmethod - def notify(action, alarm_id, alarm_name, previous, current, reason, - reason_data, headers=None): + def notify(action, alarm_id, alarm_name, severity, previous, + current, reason, reason_data, headers=None): headers = headers or {} if not headers.get('x-openstack-request-id'): headers['x-openstack-request-id'] = context.generate_request_id() @@ -66,12 +66,13 @@ class RestAlarmNotifier(notifier.AlarmNotifier): "%(previous)s to %(current)s with action %(action)s because " "%(reason)s. request-id: %(request_id)s ") % ({'alarm_name': alarm_name, 'alarm_id': alarm_id, - 'previous': previous, 'current': current, - 'action': action, 'reason': reason, + 'severity': severity, 'previous': previous, + 'current': current, 'action': action, 'reason': reason, 'request_id': headers['x-openstack-request-id']})) body = {'alarm_name': alarm_name, 'alarm_id': alarm_id, - 'previous': previous, 'current': current, - 'reason': reason, 'reason_data': reason_data} + 'severity': severity, 'previous': previous, + 'current': current, 'reason': reason, + 'reason_data': reason_data} headers['content-type'] = 'application/json' kwargs = {'data': jsonutils.dumps(body), 'headers': headers} diff --git a/ceilometer/alarm/notifier/test.py b/ceilometer/alarm/notifier/test.py index e2abd1741..a7d74eb14 100644 --- a/ceilometer/alarm/notifier/test.py +++ b/ceilometer/alarm/notifier/test.py @@ -23,11 +23,12 @@ class TestAlarmNotifier(notifier.AlarmNotifier): def __init__(self): self.notifications = [] - def notify(self, action, alarm_id, alarm_name, previous, current, - reason, reason_data): + def notify(self, action, alarm_id, alarm_name, severity, + previous, current, reason, reason_data): self.notifications.append((action, alarm_id, alarm_name, + severity, previous, current, reason, diff --git a/ceilometer/alarm/notifier/trust.py b/ceilometer/alarm/notifier/trust.py index 818ffc09b..b71eedf2b 100644 --- a/ceilometer/alarm/notifier/trust.py +++ b/ceilometer/alarm/notifier/trust.py @@ -36,7 +36,7 @@ class TrustRestAlarmNotifier(rest.RestAlarmNotifier): """ @staticmethod - def notify(action, alarm_id, alarm_name, previous, current, + def notify(action, alarm_id, alarm_name, severity, previous, current, reason, reason_data): trust_id = action.username @@ -62,5 +62,5 @@ class TrustRestAlarmNotifier(rest.RestAlarmNotifier): headers = {'X-Auth-Token': client.auth_token} rest.RestAlarmNotifier.notify( - action, alarm_id, alarm_name, previous, current, reason, + action, alarm_id, alarm_name, severity, previous, current, reason, reason_data, headers) diff --git a/ceilometer/alarm/rpc.py b/ceilometer/alarm/rpc.py index a583703b3..5f97996c8 100644 --- a/ceilometer/alarm/rpc.py +++ b/ceilometer/alarm/rpc.py @@ -65,6 +65,7 @@ class RPCAlarmNotifier(object): 'actions': actions, 'alarm_id': alarm.alarm_id, 'alarm_name': alarm.name, + 'severity': alarm.severity, 'previous': previous, 'current': alarm.state, 'reason': six.text_type(reason), diff --git a/ceilometer/alarm/service.py b/ceilometer/alarm/service.py index 087e49bfb..5984f63da 100644 --- a/ceilometer/alarm/service.py +++ b/ceilometer/alarm/service.py @@ -243,8 +243,8 @@ class AlarmNotifierService(os_service.Service): self.rpc_server.stop() super(AlarmNotifierService, self).stop() - def _handle_action(self, action, alarm_id, alarm_name, previous, - current, reason, reason_data): + def _handle_action(self, action, alarm_id, alarm_name, severity, + previous, current, reason, reason_data): try: action = netutils.urlsplit(action) except Exception: @@ -266,8 +266,8 @@ class AlarmNotifierService(os_service.Service): try: LOG.debug(_("Notifying alarm %(id)s with action %(act)s") % ( {'id': alarm_id, 'act': action})) - notifier.notify(action, alarm_id, alarm_name, previous, - current, reason, reason_data) + notifier.notify(action, alarm_id, alarm_name, severity, + previous, current, reason, reason_data) except Exception: LOG.exception(_("Unable to notify alarm %s"), alarm_id) return @@ -282,6 +282,7 @@ class AlarmNotifierService(os_service.Service): extensions automatically - alarm_id, the ID of the alarm that has been triggered - alarm_name, the name of the alarm that has been triggered + - severity, the level of the alarm that has been triggered - previous, the previous state of the alarm - current, the new state the alarm has transitioned to - reason, the reason the alarm changed its state @@ -296,6 +297,7 @@ class AlarmNotifierService(os_service.Service): self._handle_action(action, data.get('alarm_id'), data.get('alarm_name'), + data.get('severity'), data.get('previous'), data.get('current'), data.get('reason'), diff --git a/ceilometer/alarm/storage/base.py b/ceilometer/alarm/storage/base.py index 126f203df..8e4a0e4cf 100644 --- a/ceilometer/alarm/storage/base.py +++ b/ceilometer/alarm/storage/base.py @@ -42,7 +42,7 @@ class Connection(object): @staticmethod def get_alarms(name=None, user=None, state=None, meter=None, project=None, enabled=None, alarm_id=None, pagination=None, - alarm_type=None): + alarm_type=None, severity=None): """Yields a lists of alarms that match filters. :param name: Optional name for alarm. @@ -54,6 +54,7 @@ class Connection(object): :param alarm_id: Optional alarm_id to return one alarm. :param pagination: Optional pagination query. :param alarm_type: Optional alarm type. + :parmr severity: Optional alarm severity """ raise ceilometer.NotImplementedError('Alarms not implemented') @@ -78,8 +79,9 @@ class Connection(object): @staticmethod def get_alarm_changes(alarm_id, on_behalf_of, user=None, project=None, alarm_type=None, - start_timestamp=None, start_timestamp_op=None, - end_timestamp=None, end_timestamp_op=None): + severity=None, start_timestamp=None, + start_timestamp_op=None, end_timestamp=None, + end_timestamp_op=None): """Yields list of AlarmChanges describing alarm history Changes are always sorted in reverse order of occurrence, given @@ -98,6 +100,7 @@ class Connection(object): :param user: Optional ID of user to return changes for :param project: Optional ID of project to return changes for :param alarm_type: Optional change type + :param severity: Optional change severity :param start_timestamp: Optional modified timestamp start range :param start_timestamp_op: Optional timestamp start range operation :param end_timestamp: Optional modified timestamp end range diff --git a/ceilometer/alarm/storage/impl_hbase.py b/ceilometer/alarm/storage/impl_hbase.py index 7332a85fc..7b0d43929 100644 --- a/ceilometer/alarm/storage/impl_hbase.py +++ b/ceilometer/alarm/storage/impl_hbase.py @@ -120,7 +120,7 @@ class Connection(hbase_base.Connection, base.Connection): def get_alarms(self, name=None, user=None, state=None, meter=None, project=None, enabled=None, alarm_id=None, pagination=None, - alarm_type=None): + alarm_type=None, severity=None): if pagination: raise ceilometer.NotImplementedError('Pagination not implemented') @@ -131,7 +131,7 @@ class Connection(hbase_base.Connection, base.Connection): q = hbase_utils.make_query(alarm_id=alarm_id, name=name, enabled=enabled, user_id=user, project_id=project, state=state, - type=alarm_type) + type=alarm_type, severity=severity) with self.conn_pool.connection() as conn: alarm_table = conn.table(self.ALARM_TABLE) @@ -146,11 +146,13 @@ class Connection(hbase_base.Connection, base.Connection): def get_alarm_changes(self, alarm_id, on_behalf_of, user=None, project=None, alarm_type=None, - start_timestamp=None, start_timestamp_op=None, - end_timestamp=None, end_timestamp_op=None): + severity=None, start_timestamp=None, + start_timestamp_op=None, end_timestamp=None, + end_timestamp_op=None): q = hbase_utils.make_query(alarm_id=alarm_id, on_behalf_of=on_behalf_of, type=alarm_type, - user_id=user, project_id=project) + user_id=user, project_id=project, + severity=severity) start_row, end_row = hbase_utils.make_timestamp_query( hbase_utils.make_general_rowkey_scan, start=start_timestamp, start_op=start_timestamp_op, diff --git a/ceilometer/alarm/storage/impl_log.py b/ceilometer/alarm/storage/impl_log.py index ba559a06b..e7fb190df 100644 --- a/ceilometer/alarm/storage/impl_log.py +++ b/ceilometer/alarm/storage/impl_log.py @@ -32,7 +32,7 @@ class Connection(base.Connection): def get_alarms(self, name=None, user=None, state=None, meter=None, project=None, enabled=None, alarm_id=None, pagination=None, - alarm_type=None): + alarm_type=None, severity=None): """Yields a lists of alarms that match filters.""" return [] diff --git a/ceilometer/alarm/storage/impl_sqlalchemy.py b/ceilometer/alarm/storage/impl_sqlalchemy.py index 903725d70..18fbbc897 100644 --- a/ceilometer/alarm/storage/impl_sqlalchemy.py +++ b/ceilometer/alarm/storage/impl_sqlalchemy.py @@ -132,14 +132,15 @@ class Connection(base.Connection): row.insufficient_data_actions), rule=row.rule, time_constraints=row.time_constraints, - repeat_actions=row.repeat_actions) + repeat_actions=row.repeat_actions, + severity=row.severity) def _retrieve_alarms(self, query): return (self._row_to_alarm_model(x) for x in query.all()) def get_alarms(self, name=None, user=None, state=None, meter=None, project=None, enabled=None, alarm_id=None, pagination=None, - alarm_type=None): + alarm_type=None, severity=None): """Yields a lists of alarms that match filters. :param name: Optional name for alarm. @@ -151,6 +152,7 @@ class Connection(base.Connection): :param alarm_id: Optional alarm_id to return one alarm. :param pagination: Optional pagination query. :param alarm_type: Optional alarm type. + :param severity: Optional alarm severity """ if pagination: @@ -172,6 +174,8 @@ class Connection(base.Connection): query = query.filter(models.Alarm.state == state) if alarm_type is not None: query = query.filter(models.Alarm.type == alarm_type) + if severity is not None: + query = query.filter(models.Alarm.severity == severity) query = query.order_by(desc(models.Alarm.timestamp)) alarms = self._retrieve_alarms(query) @@ -246,8 +250,9 @@ class Connection(base.Connection): def get_alarm_changes(self, alarm_id, on_behalf_of, user=None, project=None, alarm_type=None, - start_timestamp=None, start_timestamp_op=None, - end_timestamp=None, end_timestamp_op=None): + severity=None, start_timestamp=None, + start_timestamp_op=None, end_timestamp=None, + end_timestamp_op=None): """Yields list of AlarmChanges describing alarm history Changes are always sorted in reverse order of occurrence, given @@ -266,6 +271,7 @@ class Connection(base.Connection): :param user: Optional ID of user to return changes for :param project: Optional ID of project to return changes for :param alarm_type: Optional change type + :param severity: Optional alarm severity :param start_timestamp: Optional modified timestamp start range :param start_timestamp_op: Optional timestamp start range operation :param end_timestamp: Optional modified timestamp end range @@ -284,6 +290,8 @@ class Connection(base.Connection): query = query.filter(models.AlarmChange.project_id == project) if alarm_type is not None: query = query.filter(models.AlarmChange.type == alarm_type) + if severity is not None: + query = query.filter(models.AlarmChange.severity == severity) if start_timestamp: if start_timestamp_op == 'gt': query = query.filter( diff --git a/ceilometer/alarm/storage/models.py b/ceilometer/alarm/storage/models.py index 822f1fd13..6865c190c 100644 --- a/ceilometer/alarm/storage/models.py +++ b/ceilometer/alarm/storage/models.py @@ -32,6 +32,10 @@ class Alarm(base.Model): ALARM_ALARM: 'alarm_actions', } + ALARM_LEVEL_LOW = 'low' + ALARM_LEVEL_MODERATE = 'moderate' + ALARM_LEVEL_CRITICAL = 'critical' + """ An alarm to monitor. @@ -56,16 +60,16 @@ class Alarm(base.Model): entering the insufficient data state :param repeat_actions: Is the actions should be triggered on each alarm evaluation. + :param severity: Alarm level (low/moderate/critical) """ def __init__(self, alarm_id, type, enabled, name, description, timestamp, user_id, project_id, state, state_timestamp, ok_actions, alarm_actions, insufficient_data_actions, - repeat_actions, rule, time_constraints): + repeat_actions, rule, time_constraints, severity=None): if not isinstance(timestamp, datetime.datetime): raise TypeError(_("timestamp should be datetime object")) if not isinstance(state_timestamp, datetime.datetime): raise TypeError(_("state_timestamp should be datetime object")) - base.Model.__init__( self, alarm_id=alarm_id, @@ -83,7 +87,8 @@ class Alarm(base.Model): insufficient_data_actions=insufficient_data_actions, repeat_actions=repeat_actions, rule=rule, - time_constraints=time_constraints) + time_constraints=time_constraints, + severity=severity) class AlarmChange(base.Model): @@ -92,6 +97,7 @@ class AlarmChange(base.Model): :param event_id: UUID of the change event :param alarm_id: UUID of the alarm :param type: The type of change + :param severity: The severity of alarm :param detail: JSON fragment describing change :param user_id: the user ID of the initiating identity :param project_id: the project ID of the initiating identity @@ -113,6 +119,7 @@ class AlarmChange(base.Model): user_id, project_id, on_behalf_of, + severity=None, timestamp=None ): base.Model.__init__( @@ -120,6 +127,7 @@ class AlarmChange(base.Model): event_id=event_id, alarm_id=alarm_id, type=type, + severity=severity, detail=detail, user_id=user_id, project_id=project_id, diff --git a/ceilometer/alarm/storage/pymongo_base.py b/ceilometer/alarm/storage/pymongo_base.py index ab8ccbd13..c097ac1da 100644 --- a/ceilometer/alarm/storage/pymongo_base.py +++ b/ceilometer/alarm/storage/pymongo_base.py @@ -81,7 +81,7 @@ class Connection(base.Connection): def get_alarms(self, name=None, user=None, state=None, meter=None, project=None, enabled=None, alarm_id=None, pagination=None, - alarm_type=None): + alarm_type=None, severity=None): """Yields a lists of alarms that match filters. :param name: Optional name for alarm. @@ -93,6 +93,7 @@ class Connection(base.Connection): :param alarm_id: Optional alarm_id to return one alarm. :param pagination: Optional pagination query. :param alarm_type: Optional alarm type. + :param severity: Optional alarm severity. """ if pagination: raise ceilometer.NotImplementedError('Pagination not implemented') @@ -114,6 +115,8 @@ class Connection(base.Connection): q['rule.meter_name'] = meter if alarm_type is not None: q['type'] = alarm_type + if severity is not None: + q['severity'] = severity return self._retrieve_alarms(q, [("timestamp", @@ -122,8 +125,9 @@ class Connection(base.Connection): def get_alarm_changes(self, alarm_id, on_behalf_of, user=None, project=None, alarm_type=None, - start_timestamp=None, start_timestamp_op=None, - end_timestamp=None, end_timestamp_op=None): + severity=None, start_timestamp=None, + start_timestamp_op=None, end_timestamp=None, + end_timestamp_op=None): """Yields list of AlarmChanges describing alarm history Changes are always sorted in reverse order of occurrence, given @@ -142,6 +146,7 @@ class Connection(base.Connection): :param user: Optional ID of user to return changes for :param project: Optional ID of project to return changes for :param alarm_type: Optional change type + :param severity: Optional change severity :param start_timestamp: Optional modified timestamp start range :param start_timestamp_op: Optional timestamp start range operation :param end_timestamp: Optional modified timestamp end range @@ -156,6 +161,8 @@ class Connection(base.Connection): q['project_id'] = project if alarm_type is not None: q['type'] = alarm_type + if severity is not None: + q['severity'] = severity if start_timestamp or end_timestamp: ts_range = pymongo_utils.make_timestamp_range(start_timestamp, end_timestamp, diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py index 90cfd9e74..5cc4f1fe9 100644 --- a/ceilometer/api/controllers/v2.py +++ b/ceilometer/api/controllers/v2.py @@ -86,6 +86,8 @@ state_kind = ["ok", "alarm", "insufficient data"] state_kind_enum = wtypes.Enum(str, *state_kind) operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt') operation_kind_enum = wtypes.Enum(str, *operation_kind) +severity_kind = ["low", "moderate", "critical"] +severity_kind_enum = wtypes.Enum(str, *severity_kind) class ClientSideError(wsme.exc.ClientSideError): @@ -1832,6 +1834,10 @@ class Alarm(_Base): state_timestamp = datetime.datetime "The date of the last alarm state changed" + severity = AdvEnum('severity', str, *severity_kind, + default='low') + "The severity of the alarm" + def __init__(self, rule=None, time_constraints=None, **kwargs): super(Alarm, self).__init__(**kwargs) @@ -1920,6 +1926,7 @@ class Alarm(_Base): enabled=True, timestamp=datetime.datetime.utcnow(), state="ok", + severity="moderate", state_timestamp=datetime.datetime.utcnow(), ok_actions=["http://site:8000/ok"], alarm_actions=["http://site:8000/alarm"], @@ -2051,6 +2058,7 @@ class AlarmController(rest.RestController): now = timeutils.utcnow() data.alarm_id = self._id + user, project = rbac.get_limited_to(pecan.request.headers) if user: data.user_id = user @@ -2065,7 +2073,7 @@ class AlarmController(rest.RestController): data.state_timestamp = now else: data.state_timestamp = alarm_in.state_timestamp - + alarm_in.severity = data.severity # make sure alarms are unique by name per project. if alarm_in.name != data.name: alarms = list(self.conn.get_alarms(name=data.name, diff --git a/ceilometer/storage/sqlalchemy/migrate_repo/versions/040_add_alarm_severity.py b/ceilometer/storage/sqlalchemy/migrate_repo/versions/040_add_alarm_severity.py new file mode 100644 index 000000000..20fd1fc65 --- /dev/null +++ b/ceilometer/storage/sqlalchemy/migrate_repo/versions/040_add_alarm_severity.py @@ -0,0 +1,31 @@ +# +# 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 sqlalchemy import Column +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + + +def upgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + alarm = Table('alarm', meta, autoload=True) + severity = Column('severity', String(50)) + alarm.create_column(severity) + + +def downgrade(migrate_engine): + meta = MetaData(bind=migrate_engine) + alarm = Table('alarm', meta, autoload=True) + severity = Column('severity', String(50)) + alarm.drop_column(severity) diff --git a/ceilometer/storage/sqlalchemy/models.py b/ceilometer/storage/sqlalchemy/models.py index 6285574ef..d5118234c 100644 --- a/ceilometer/storage/sqlalchemy/models.py +++ b/ceilometer/storage/sqlalchemy/models.py @@ -259,6 +259,7 @@ class Alarm(Base): enabled = Column(Boolean) name = Column(Text) type = Column(String(50)) + severity = Column(String(50)) description = Column(Text) timestamp = Column(PreciseTimestamp, default=lambda: timeutils.utcnow()) diff --git a/ceilometer/tests/alarm/evaluator/test_combination.py b/ceilometer/tests/alarm/evaluator/test_combination.py index 315faa497..54c9e3a9e 100644 --- a/ceilometer/tests/alarm/evaluator/test_combination.py +++ b/ceilometer/tests/alarm/evaluator/test_combination.py @@ -58,7 +58,8 @@ class TestEvaluate(base.TestEvaluatorBase): '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', @@ -79,7 +80,8 @@ class TestEvaluate(base.TestEvaluatorBase): 'b82734f4-9d06-48f3-8a86-fa59a0c99dc8', '15a700e5-2fe8-4b3d-8c55-9e92831f6a2b'], operator='and', - )) + ), + severity='critical') ] @staticmethod diff --git a/ceilometer/tests/alarm/evaluator/test_threshold.py b/ceilometer/tests/alarm/evaluator/test_threshold.py index c6d799628..5bd379234 100644 --- a/ceilometer/tests/alarm/evaluator/test_threshold.py +++ b/ceilometer/tests/alarm/evaluator/test_threshold.py @@ -63,7 +63,8 @@ class TestEvaluate(base.TestEvaluatorBase): 'value': 'cpu_util'}, {'field': 'resource_id', 'op': 'eq', - 'value': 'my_instance'}]) + 'value': 'my_instance'}]), + severity='critical' ), models.Alarm(name='group_running_idle', description='group_running_idle', @@ -92,7 +93,8 @@ class TestEvaluate(base.TestEvaluatorBase): 'value': 'cpu_util'}, {'field': 'metadata.user_metadata.AS', 'op': 'eq', - 'value': 'my_group'}]) + 'value': 'my_group'}]), + severity='critical' ), ] diff --git a/ceilometer/tests/alarm/partition/test_coordination.py b/ceilometer/tests/alarm/partition/test_coordination.py index 11f692892..dd3a1c6f5 100644 --- a/ceilometer/tests/alarm/partition/test_coordination.py +++ b/ceilometer/tests/alarm/partition/test_coordination.py @@ -121,6 +121,7 @@ class TestCoordinate(tests_base.BaseTestCase): alarm_actions=[], insufficient_data_actions=[], alarm_id=uuid, + severity='critical', time_constraints=[], rule=dict( statistic='avg', diff --git a/ceilometer/tests/alarm/test_notifier.py b/ceilometer/tests/alarm/test_notifier.py index 1fb8c8d6f..2e3a9a7dd 100644 --- a/ceilometer/tests/alarm/test_notifier.py +++ b/ceilometer/tests/alarm/test_notifier.py @@ -28,11 +28,12 @@ from ceilometer.tests import base as tests_base DATA_JSON = jsonutils.loads( '{"current": "ALARM", "alarm_id": "foobar", "alarm_name": "testalarm",' - ' "reason": "what ?", "reason_data": {"test": "test"},' - ' "previous": "OK"}' + ' "severity": "critical", "reason": "what ?",' + ' "reason_data": {"test": "test"}, "previous": "OK"}' ) NOTIFICATION = dict(alarm_id='foobar', alarm_name='testalarm', + severity='critical', condition=dict(threshold=42), reason='what ?', reason_data={'test': 'test'}, @@ -64,6 +65,7 @@ class TestAlarmNotifier(tests_base.BaseTestCase): 'actions': ['test://'], 'alarm_id': 'foobar', 'alarm_name': 'testalarm', + 'severity': 'critical', 'previous': 'OK', 'current': 'ALARM', 'reason': 'Everything is on fire', @@ -75,6 +77,7 @@ class TestAlarmNotifier(tests_base.BaseTestCase): self.assertEqual((urlparse.urlsplit(data['actions'][0]), data['alarm_id'], data['alarm_name'], + data['severity'], data['previous'], data['current'], data['reason'], diff --git a/ceilometer/tests/alarm/test_rpc.py b/ceilometer/tests/alarm/test_rpc.py index bca284f05..c7b4382e8 100644 --- a/ceilometer/tests/alarm/test_rpc.py +++ b/ceilometer/tests/alarm/test_rpc.py @@ -67,6 +67,7 @@ class TestRPCAlarmNotifier(tests_base.BaseTestCase): 'project_id': 'snafu', 'period': 60, 'alarm_id': str(uuid.uuid4()), + 'severity': 'critical', 'matching_metadata':{'resource_id': 'my_instance'} }), @@ -83,6 +84,7 @@ class TestRPCAlarmNotifier(tests_base.BaseTestCase): 'project_id': 'snafu', 'period': 300, 'alarm_id': str(uuid.uuid4()), + 'severity': 'critical', 'matching_metadata':{'metadata.user_metadata.AS': 'my_group'} }), @@ -109,6 +111,8 @@ class TestRPCAlarmNotifier(tests_base.BaseTestCase): self.notifier_server.notified[i]["alarm_id"]) self.assertEqual(self.alarms[i].name, self.notifier_server.notified[i]["alarm_name"]) + self.assertEqual(self.alarms[i].severity, + self.notifier_server.notified[i]["severity"]) self.assertEqual(actions, self.notifier_server.notified[i]["actions"]) self.assertEqual(previous[i], diff --git a/ceilometer/tests/api/v2/test_alarm_scenarios.py b/ceilometer/tests/api/v2/test_alarm_scenarios.py index c8c9af9b5..93cdcc972 100644 --- a/ceilometer/tests/api/v2/test_alarm_scenarios.py +++ b/ceilometer/tests/api/v2/test_alarm_scenarios.py @@ -52,6 +52,7 @@ class TestAlarms(v2.FunctionalTest, alarm_id='a', description='a', state='insufficient data', + severity='critical', state_timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME, ok_actions=[], @@ -72,7 +73,7 @@ class TestAlarms(v2.FunctionalTest, query=[{'field': 'project_id', 'op': 'eq', 'value': self.auth_headers['X-Project-Id']} - ]) + ]), ), models.Alarm(name='name2', type='threshold', @@ -80,6 +81,7 @@ class TestAlarms(v2.FunctionalTest, alarm_id='b', description='b', state='insufficient data', + severity='critical', state_timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME, ok_actions=[], @@ -98,7 +100,7 @@ class TestAlarms(v2.FunctionalTest, query=[{'field': 'project_id', 'op': 'eq', 'value': self.auth_headers['X-Project-Id']} - ]) + ]), ), models.Alarm(name='name3', type='threshold', @@ -106,6 +108,7 @@ class TestAlarms(v2.FunctionalTest, alarm_id='c', description='c', state='insufficient data', + severity='moderate', state_timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME, ok_actions=[], @@ -124,7 +127,7 @@ class TestAlarms(v2.FunctionalTest, query=[{'field': 'project_id', 'op': 'eq', 'value': self.auth_headers['X-Project-Id']} - ]) + ]), ), models.Alarm(name='name4', type='combination', @@ -132,6 +135,7 @@ class TestAlarms(v2.FunctionalTest, alarm_id='d', description='d', state='insufficient data', + severity='low', state_timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME, ok_actions=[], @@ -142,7 +146,7 @@ class TestAlarms(v2.FunctionalTest, project_id=self.auth_headers['X-Project-Id'], time_constraints=[], rule=dict(alarm_ids=['a', 'b'], - operator='or') + operator='or'), )]: self.alarm_conn.update_alarm(alarm) @@ -218,7 +222,8 @@ class TestAlarms(v2.FunctionalTest, 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(alarm_ids=['a', 'b'], operator='or'), + severity='critical') self.alarm_conn.update_alarm(alarm) resp = self.get_json('/alarms', q=[{'field': 'state', @@ -277,7 +282,8 @@ class TestAlarms(v2.FunctionalTest, 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(alarm_ids=['a', 'b'], operator='or'), + severity='critical') self.alarm_conn.update_alarm(alarm) alarms = self.get_json('/alarms', @@ -566,6 +572,27 @@ class TestAlarms(v2.FunctionalTest, alarms = list(self.alarm_conn.get_alarms()) self.assertEqual(4, len(alarms)) + def test_post_invalid_alarm_input_severity(self): + json = { + 'name': 'alarm1', + 'state': 'ok', + 'severity': 'bad_value', + 'type': 'threshold', + 'threshold_rule': { + 'meter_name': 'ameter', + 'comparison_operator': 'gt', + 'threshold': 50.0 + } + } + resp = self.post_json('/alarms', params=json, expect_errors=True, + status=400, headers=self.auth_headers) + expected_err_msg = ("Invalid input for field/attribute severity." + " Value: 'bad_value'.") + 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_invalid_alarm_input_comparison_operator(self): json = { 'name': 'alarm2', @@ -924,6 +951,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'added_alarm', 'state': 'ok', 'type': 'threshold', + 'severity': 'low', 'ok_actions': ['http://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -971,6 +999,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'added_alarm', 'state': 'ok', 'type': 'threshold', + 'severity': 'low', 'ok_actions': ['http://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -1539,6 +1568,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'name_put', 'state': 'ok', 'type': 'threshold', + 'severity': 'critical', 'ok_actions': ['http://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -1581,6 +1611,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'name_put', 'state': 'ok', 'type': 'threshold', + 'severity': 'critical', 'ok_actions': ['http://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -1630,6 +1661,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'name1', 'state': 'ok', 'type': 'threshold', + 'severity': 'critical', 'ok_actions': ['http://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -1666,6 +1698,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'name1', 'state': 'ok', 'type': 'threshold', + 'severity': 'critical', 'ok_actions': ['http://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -1704,6 +1737,7 @@ class TestAlarms(v2.FunctionalTest, 'name': 'name1', 'state': 'ok', 'type': 'threshold', + 'severity': 'critical', 'ok_actions': ['spam://something/ok'], 'alarm_actions': ['http://something/alarm'], 'insufficient_data_actions': ['http://something/no'], @@ -2154,10 +2188,11 @@ class TestAlarms(v2.FunctionalTest, query = dict(field='alarm_id', op='eq', value='b') resp = self._get_alarm_history(alarm, query=query, expect_errors=True, status=400) - self.assertEqual('Unknown argument: "alarm_id": unrecognized' + self.assertEqual(u'Unknown argument: "alarm_id": unrecognized' " field in query: [], valid keys: ['project', " - "'search_offset', 'timestamp', 'type', 'user']", + "'search_offset', 'severity', 'timestamp'," + " 'type', 'user']", resp.json['error_message']['faultstring']) def test_get_alarm_history_constrained_by_not_supported_rule(self): @@ -2165,10 +2200,11 @@ class TestAlarms(v2.FunctionalTest, query = dict(field='abcd', op='eq', value='abcd') resp = self._get_alarm_history(alarm, query=query, expect_errors=True, status=400) - self.assertEqual('Unknown argument: "abcd": unrecognized' + self.assertEqual(u'Unknown argument: "abcd": unrecognized' " field in query: [], valid keys: ['project', " - "'search_offset', 'timestamp', 'type', 'user']", + "'search_offset', 'severity', 'timestamp'," + " 'type', 'user']", resp.json['error_message']['faultstring']) def test_get_nonexistent_alarm_history(self): @@ -2182,6 +2218,7 @@ class TestAlarms(v2.FunctionalTest, json = { 'name': 'sent_notification', 'type': 'threshold', + 'severity': 'low', 'threshold_rule': { 'meter_name': 'ameter', 'comparison_operator': 'gt', diff --git a/ceilometer/tests/api/v2/test_complex_query_scenarios.py b/ceilometer/tests/api/v2/test_complex_query_scenarios.py index cf8bb1f92..444ac4b30 100644 --- a/ceilometer/tests/api/v2/test_complex_query_scenarios.py +++ b/ceilometer/tests/api/v2/test_complex_query_scenarios.py @@ -351,7 +351,8 @@ class TestQueryAlarmsController(tests_api.FunctionalTest, 'project_id', 'op': 'eq', 'value': - project_id}])) + project_id}]), + severity='critical') self.alarm_conn.update_alarm(alarm) def test_query_all(self): diff --git a/ceilometer/tests/api/v2/test_query.py b/ceilometer/tests/api/v2/test_query.py index 31cdba66a..ad80b66b2 100644 --- a/ceilometer/tests/api/v2/test_query.py +++ b/ceilometer/tests/api/v2/test_query.py @@ -345,7 +345,7 @@ class TestQueryToKwArgs(tests_base.BaseTestCase): api._query_to_kwargs, q, alarm_storage_base.Connection.get_alarm_changes) valid_keys = ['alarm_id', 'on_behalf_of', 'project', 'search_offset', - 'timestamp', 'type', 'user'] + 'severity', 'timestamp', 'type', 'user'] msg = ("unrecognized field in query: %s, " "valid keys: %s") % (q, valid_keys) expected_exc = wsme.exc.UnknownArgument('abc', msg) @@ -402,7 +402,7 @@ class TestQueryToKwArgs(tests_base.BaseTestCase): wsme.exc.UnknownArgument, api._query_to_kwargs, q, alarm_storage_base.Connection.get_alarms) valid_keys = ['alarm_id', 'enabled', 'meter', 'name', 'pagination', - 'project', 'state', 'type', 'user'] + 'project', 'severity', 'state', 'type', 'user'] msg = ("unrecognized field in query: %s, " "valid keys: %s") % (q, valid_keys) expected_exc = wsme.exc.UnknownArgument('abc', msg) diff --git a/ceilometer/tests/storage/test_models.py b/ceilometer/tests/storage/test_models.py index c3f1ce4c2..2115ba4ff 100644 --- a/ceilometer/tests/storage/test_models.py +++ b/ceilometer/tests/storage/test_models.py @@ -75,15 +75,15 @@ class ModelTest(testbase.BaseTestCase): "timestamp", "user_id", "project_id", "state", "state_timestamp", "ok_actions", "alarm_actions", "insufficient_data_actions", "repeat_actions", "rule", - "time_constraints"] + "severity", "time_constraints"] self.assertEqual(set(alarm_fields), set(alarm_models.Alarm.get_field_names())) def test_get_field_names_of_alarmchange(self): alarmchange_fields = ["event_id", "alarm_id", "type", "detail", - "user_id", "project_id", "on_behalf_of", - "timestamp"] + "user_id", "project_id", "severity", + "on_behalf_of", "timestamp"] self.assertEqual(set(alarmchange_fields), set(alarm_models.AlarmChange.get_field_names()))