From 900190f97be1fa2515222bf5c583b8a29eb4bbba Mon Sep 17 00:00:00 2001 From: Pradeep Kilambi Date: Thu, 18 Dec 2014 09:20:42 -0800 Subject: [PATCH] Expose alarm severity in Alarm Model This commit enables us to set a severity to an alarm. This will greatly help from auditing standpoint on which alarms are low/moderate/critical when they were triggered. Change-Id: I5a0d3cf4d5736983a8c8354310360d2b41892965 Implements: blueprint ceilometer-alarm-level --- ceilometer/alarm/notifier/__init__.py | 5 +- ceilometer/alarm/notifier/log.py | 18 +++--- ceilometer/alarm/notifier/rest.py | 13 +++-- ceilometer/alarm/notifier/test.py | 5 +- ceilometer/alarm/notifier/trust.py | 4 +- ceilometer/alarm/rpc.py | 1 + ceilometer/alarm/service.py | 10 ++-- ceilometer/alarm/storage/base.py | 9 ++- ceilometer/alarm/storage/impl_hbase.py | 12 ++-- ceilometer/alarm/storage/impl_log.py | 2 +- ceilometer/alarm/storage/impl_sqlalchemy.py | 16 ++++-- ceilometer/alarm/storage/models.py | 14 ++++- ceilometer/alarm/storage/pymongo_base.py | 13 ++++- ceilometer/api/controllers/v2.py | 10 +++- .../versions/040_add_alarm_severity.py | 31 ++++++++++ ceilometer/storage/sqlalchemy/models.py | 1 + .../tests/alarm/evaluator/test_combination.py | 6 +- .../tests/alarm/evaluator/test_threshold.py | 6 +- .../alarm/partition/test_coordination.py | 1 + ceilometer/tests/alarm/test_notifier.py | 7 ++- ceilometer/tests/alarm/test_rpc.py | 4 ++ .../tests/api/v2/test_alarm_scenarios.py | 57 +++++++++++++++---- .../api/v2/test_complex_query_scenarios.py | 3 +- ceilometer/tests/api/v2/test_query.py | 4 +- ceilometer/tests/storage/test_models.py | 6 +- 25 files changed, 193 insertions(+), 65 deletions(-) create mode 100644 ceilometer/storage/sqlalchemy/migrate_repo/versions/040_add_alarm_severity.py diff --git a/ceilometer/alarm/notifier/__init__.py b/ceilometer/alarm/notifier/__init__.py index 80a0f0858..d30d3c1b1 100644 --- a/ceilometer/alarm/notifier/__init__.py +++ b/ceilometer/alarm/notifier/__init__.py @@ -25,13 +25,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 9777f1119..d0e4e78f4 100644 --- a/ceilometer/alarm/notifier/log.py +++ b/ceilometer/alarm/notifier/log.py @@ -27,11 +27,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 44dc986b4..12784428e 100644 --- a/ceilometer/alarm/notifier/rest.py +++ b/ceilometer/alarm/notifier/rest.py @@ -57,8 +57,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() @@ -68,12 +68,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 4a64b9d58..5f8b06488 100644 --- a/ceilometer/alarm/notifier/test.py +++ b/ceilometer/alarm/notifier/test.py @@ -25,11 +25,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 3a8f574f0..6ef4e7750 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 6509a2325..c570c9fd2 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 9b2d3496c..bf2f09453 100644 --- a/ceilometer/alarm/service.py +++ b/ceilometer/alarm/service.py @@ -253,8 +253,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: @@ -276,8 +276,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 @@ -292,6 +292,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 @@ -306,6 +307,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 17945bf06..0cda42b44 100644 --- a/ceilometer/alarm/storage/base.py +++ b/ceilometer/alarm/storage/base.py @@ -45,7 +45,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. @@ -57,6 +57,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') @@ -81,8 +82,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 @@ -101,6 +103,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 0b8675b23..b0d486015 100644 --- a/ceilometer/alarm/storage/impl_log.py +++ b/ceilometer/alarm/storage/impl_log.py @@ -34,7 +34,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 18dc4d122..004dfaf62 100644 --- a/ceilometer/alarm/storage/impl_sqlalchemy.py +++ b/ceilometer/alarm/storage/impl_sqlalchemy.py @@ -136,14 +136,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. @@ -155,6 +156,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: @@ -176,6 +178,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) @@ -250,8 +254,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 @@ -270,6 +275,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 @@ -288,6 +294,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 620161b39..185e5feae 100644 --- a/ceilometer/alarm/storage/models.py +++ b/ceilometer/alarm/storage/models.py @@ -34,6 +34,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. @@ -58,16 +62,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, @@ -85,7 +89,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): @@ -94,6 +99,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 @@ -115,6 +121,7 @@ class AlarmChange(base.Model): user_id, project_id, on_behalf_of, + severity=None, timestamp=None ): base.Model.__init__( @@ -122,6 +129,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 53231d3f3..75ce2a703 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): @@ -1831,6 +1833,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) @@ -1919,6 +1925,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"], @@ -2050,6 +2057,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 @@ -2064,7 +2072,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 e3e93b953..d0d28154e 100644 --- a/ceilometer/storage/sqlalchemy/models.py +++ b/ceilometer/storage/sqlalchemy/models.py @@ -258,6 +258,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 220a33759..7930eed78 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 cb63ebbe4..4c572e5b9 100644 --- a/ceilometer/tests/alarm/evaluator/test_threshold.py +++ b/ceilometer/tests/alarm/evaluator/test_threshold.py @@ -65,7 +65,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', @@ -94,7 +95,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 7e6e6c702..6853ce56c 100644 --- a/ceilometer/tests/alarm/partition/test_coordination.py +++ b/ceilometer/tests/alarm/partition/test_coordination.py @@ -123,6 +123,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 82681ab24..7bf775085 100644 --- a/ceilometer/tests/alarm/test_notifier.py +++ b/ceilometer/tests/alarm/test_notifier.py @@ -29,11 +29,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'}, @@ -65,6 +66,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', @@ -76,6 +78,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 d2d8ce156..d52f66a6e 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 e3e22d35e..ca457050c 100644 --- a/ceilometer/tests/api/v2/test_alarm_scenarios.py +++ b/ceilometer/tests/api/v2/test_alarm_scenarios.py @@ -56,6 +56,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=[], @@ -76,7 +77,7 @@ class TestAlarms(v2.FunctionalTest, query=[{'field': 'project_id', 'op': 'eq', 'value': self.auth_headers['X-Project-Id']} - ]) + ]), ), models.Alarm(name='name2', type='threshold', @@ -84,6 +85,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=[], @@ -102,7 +104,7 @@ class TestAlarms(v2.FunctionalTest, query=[{'field': 'project_id', 'op': 'eq', 'value': self.auth_headers['X-Project-Id']} - ]) + ]), ), models.Alarm(name='name3', type='threshold', @@ -110,6 +112,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=[], @@ -128,7 +131,7 @@ class TestAlarms(v2.FunctionalTest, query=[{'field': 'project_id', 'op': 'eq', 'value': self.auth_headers['X-Project-Id']} - ]) + ]), ), models.Alarm(name='name4', type='combination', @@ -136,6 +139,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=[], @@ -146,7 +150,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) @@ -222,7 +226,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', @@ -281,7 +286,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', @@ -570,6 +576,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', @@ -928,6 +955,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'], @@ -975,6 +1003,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'], @@ -1543,6 +1572,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'], @@ -1585,6 +1615,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'], @@ -1634,6 +1665,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'], @@ -1670,6 +1702,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'], @@ -1708,6 +1741,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'], @@ -2158,10 +2192,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): @@ -2169,10 +2204,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): @@ -2186,6 +2222,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 2daa602cd..8460daa4e 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 afc5ea859..2fc84cfa9 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 1fc2627d4..671ccb779 100644 --- a/ceilometer/tests/storage/test_models.py +++ b/ceilometer/tests/storage/test_models.py @@ -77,15 +77,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()))