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
This commit is contained in:
Pradeep Kilambi 2014-12-18 09:20:42 -08:00
parent d68f8afcc9
commit 900190f97b
25 changed files with 193 additions and 65 deletions

View File

@ -25,13 +25,14 @@ class AlarmNotifier(object):
"""Base class for alarm notifier plugins.""" """Base class for alarm notifier plugins."""
@abc.abstractmethod @abc.abstractmethod
def notify(self, action, alarm_id, alarm_name, previous, current, def notify(self, action, alarm_id, alarm_name, severity, previous,
reason, reason_data): current, reason, reason_data):
"""Notify that an alarm has been triggered. """Notify that an alarm has been triggered.
:param action: The action that is being attended, as a parsed URL. :param action: The action that is being attended, as a parsed URL.
:param alarm_id: The triggered alarm. :param alarm_id: The triggered alarm.
:param alarm_name: The name of 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 previous: The previous state of the alarm.
:param current: The current state of the alarm. :param current: The current state of the alarm.
:param reason: The reason the alarm changed its state. :param reason: The reason the alarm changed its state.

View File

@ -27,11 +27,15 @@ class LogAlarmNotifier(notifier.AlarmNotifier):
"Log alarm notifier.""" "Log alarm notifier."""
@staticmethod @staticmethod
def notify(action, alarm_id, alarm_name, previous, current, reason, def notify(action, alarm_id, alarm_name, severity, previous, current,
reason_data): reason, reason_data):
LOG.info(_( LOG.info(_(
"Notifying alarm %(alarm_name)s %(alarm_id)s from %(previous)s " "Notifying alarm %(alarm_name)s %(alarm_id)s of %(severity)s "
"to %(current)s with action %(action)s because " "priority from %(previous)s to %(current)s with action %(action)s"
"%(reason)s.") % ({'alarm_name': alarm_name, 'alarm_id': alarm_id, " because %(reason)s.") % ({'alarm_name': alarm_name,
'previous': previous, 'current': current, 'alarm_id': alarm_id,
'action': action, 'reason': reason})) 'severity': severity,
'previous': previous,
'current': current,
'action': action,
'reason': reason}))

View File

@ -57,8 +57,8 @@ class RestAlarmNotifier(notifier.AlarmNotifier):
"""Rest alarm notifier.""" """Rest alarm notifier."""
@staticmethod @staticmethod
def notify(action, alarm_id, alarm_name, previous, current, reason, def notify(action, alarm_id, alarm_name, severity, previous,
reason_data, headers=None): current, reason, reason_data, headers=None):
headers = headers or {} headers = headers or {}
if not headers.get('x-openstack-request-id'): if not headers.get('x-openstack-request-id'):
headers['x-openstack-request-id'] = context.generate_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 " "%(previous)s to %(current)s with action %(action)s because "
"%(reason)s. request-id: %(request_id)s ") % "%(reason)s. request-id: %(request_id)s ") %
({'alarm_name': alarm_name, 'alarm_id': alarm_id, ({'alarm_name': alarm_name, 'alarm_id': alarm_id,
'previous': previous, 'current': current, 'severity': severity, 'previous': previous,
'action': action, 'reason': reason, 'current': current, 'action': action, 'reason': reason,
'request_id': headers['x-openstack-request-id']})) 'request_id': headers['x-openstack-request-id']}))
body = {'alarm_name': alarm_name, 'alarm_id': alarm_id, body = {'alarm_name': alarm_name, 'alarm_id': alarm_id,
'previous': previous, 'current': current, 'severity': severity, 'previous': previous,
'reason': reason, 'reason_data': reason_data} 'current': current, 'reason': reason,
'reason_data': reason_data}
headers['content-type'] = 'application/json' headers['content-type'] = 'application/json'
kwargs = {'data': jsonutils.dumps(body), kwargs = {'data': jsonutils.dumps(body),
'headers': headers} 'headers': headers}

View File

@ -25,11 +25,12 @@ class TestAlarmNotifier(notifier.AlarmNotifier):
def __init__(self): def __init__(self):
self.notifications = [] self.notifications = []
def notify(self, action, alarm_id, alarm_name, previous, current, def notify(self, action, alarm_id, alarm_name, severity,
reason, reason_data): previous, current, reason, reason_data):
self.notifications.append((action, self.notifications.append((action,
alarm_id, alarm_id,
alarm_name, alarm_name,
severity,
previous, previous,
current, current,
reason, reason,

View File

@ -36,7 +36,7 @@ class TrustRestAlarmNotifier(rest.RestAlarmNotifier):
""" """
@staticmethod @staticmethod
def notify(action, alarm_id, alarm_name, previous, current, def notify(action, alarm_id, alarm_name, severity, previous, current,
reason, reason_data): reason, reason_data):
trust_id = action.username trust_id = action.username
@ -62,5 +62,5 @@ class TrustRestAlarmNotifier(rest.RestAlarmNotifier):
headers = {'X-Auth-Token': client.auth_token} headers = {'X-Auth-Token': client.auth_token}
rest.RestAlarmNotifier.notify( rest.RestAlarmNotifier.notify(
action, alarm_id, alarm_name, previous, current, reason, action, alarm_id, alarm_name, severity, previous, current, reason,
reason_data, headers) reason_data, headers)

View File

@ -65,6 +65,7 @@ class RPCAlarmNotifier(object):
'actions': actions, 'actions': actions,
'alarm_id': alarm.alarm_id, 'alarm_id': alarm.alarm_id,
'alarm_name': alarm.name, 'alarm_name': alarm.name,
'severity': alarm.severity,
'previous': previous, 'previous': previous,
'current': alarm.state, 'current': alarm.state,
'reason': six.text_type(reason), 'reason': six.text_type(reason),

View File

@ -253,8 +253,8 @@ class AlarmNotifierService(os_service.Service):
self.rpc_server.stop() self.rpc_server.stop()
super(AlarmNotifierService, self).stop() super(AlarmNotifierService, self).stop()
def _handle_action(self, action, alarm_id, alarm_name, previous, def _handle_action(self, action, alarm_id, alarm_name, severity,
current, reason, reason_data): previous, current, reason, reason_data):
try: try:
action = netutils.urlsplit(action) action = netutils.urlsplit(action)
except Exception: except Exception:
@ -276,8 +276,8 @@ class AlarmNotifierService(os_service.Service):
try: try:
LOG.debug(_("Notifying alarm %(id)s with action %(act)s") % ( LOG.debug(_("Notifying alarm %(id)s with action %(act)s") % (
{'id': alarm_id, 'act': action})) {'id': alarm_id, 'act': action}))
notifier.notify(action, alarm_id, alarm_name, previous, notifier.notify(action, alarm_id, alarm_name, severity,
current, reason, reason_data) previous, current, reason, reason_data)
except Exception: except Exception:
LOG.exception(_("Unable to notify alarm %s"), alarm_id) LOG.exception(_("Unable to notify alarm %s"), alarm_id)
return return
@ -292,6 +292,7 @@ class AlarmNotifierService(os_service.Service):
extensions automatically extensions automatically
- alarm_id, the ID of the alarm that has been triggered - alarm_id, the ID of the alarm that has been triggered
- alarm_name, the name 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 - previous, the previous state of the alarm
- current, the new state the alarm has transitioned to - current, the new state the alarm has transitioned to
- reason, the reason the alarm changed its state - reason, the reason the alarm changed its state
@ -306,6 +307,7 @@ class AlarmNotifierService(os_service.Service):
self._handle_action(action, self._handle_action(action,
data.get('alarm_id'), data.get('alarm_id'),
data.get('alarm_name'), data.get('alarm_name'),
data.get('severity'),
data.get('previous'), data.get('previous'),
data.get('current'), data.get('current'),
data.get('reason'), data.get('reason'),

View File

@ -45,7 +45,7 @@ class Connection(object):
@staticmethod @staticmethod
def get_alarms(name=None, user=None, state=None, meter=None, def get_alarms(name=None, user=None, state=None, meter=None,
project=None, enabled=None, alarm_id=None, pagination=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. """Yields a lists of alarms that match filters.
:param name: Optional name for alarm. :param name: Optional name for alarm.
@ -57,6 +57,7 @@ class Connection(object):
:param alarm_id: Optional alarm_id to return one alarm. :param alarm_id: Optional alarm_id to return one alarm.
:param pagination: Optional pagination query. :param pagination: Optional pagination query.
:param alarm_type: Optional alarm type. :param alarm_type: Optional alarm type.
:parmr severity: Optional alarm severity
""" """
raise ceilometer.NotImplementedError('Alarms not implemented') raise ceilometer.NotImplementedError('Alarms not implemented')
@ -81,8 +82,9 @@ class Connection(object):
@staticmethod @staticmethod
def get_alarm_changes(alarm_id, on_behalf_of, def get_alarm_changes(alarm_id, on_behalf_of,
user=None, project=None, alarm_type=None, user=None, project=None, alarm_type=None,
start_timestamp=None, start_timestamp_op=None, severity=None, start_timestamp=None,
end_timestamp=None, end_timestamp_op=None): start_timestamp_op=None, end_timestamp=None,
end_timestamp_op=None):
"""Yields list of AlarmChanges describing alarm history """Yields list of AlarmChanges describing alarm history
Changes are always sorted in reverse order of occurrence, given 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 user: Optional ID of user to return changes for
:param project: Optional ID of project to return changes for :param project: Optional ID of project to return changes for
:param alarm_type: Optional change type :param alarm_type: Optional change type
:param severity: Optional change severity
:param start_timestamp: Optional modified timestamp start range :param start_timestamp: Optional modified timestamp start range
:param start_timestamp_op: Optional timestamp start range operation :param start_timestamp_op: Optional timestamp start range operation
:param end_timestamp: Optional modified timestamp end range :param end_timestamp: Optional modified timestamp end range

View File

@ -120,7 +120,7 @@ class Connection(hbase_base.Connection, base.Connection):
def get_alarms(self, name=None, user=None, state=None, meter=None, def get_alarms(self, name=None, user=None, state=None, meter=None,
project=None, enabled=None, alarm_id=None, pagination=None, project=None, enabled=None, alarm_id=None, pagination=None,
alarm_type=None): alarm_type=None, severity=None):
if pagination: if pagination:
raise ceilometer.NotImplementedError('Pagination not implemented') 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, q = hbase_utils.make_query(alarm_id=alarm_id, name=name,
enabled=enabled, user_id=user, enabled=enabled, user_id=user,
project_id=project, state=state, project_id=project, state=state,
type=alarm_type) type=alarm_type, severity=severity)
with self.conn_pool.connection() as conn: with self.conn_pool.connection() as conn:
alarm_table = conn.table(self.ALARM_TABLE) 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, def get_alarm_changes(self, alarm_id, on_behalf_of,
user=None, project=None, alarm_type=None, user=None, project=None, alarm_type=None,
start_timestamp=None, start_timestamp_op=None, severity=None, start_timestamp=None,
end_timestamp=None, end_timestamp_op=None): start_timestamp_op=None, end_timestamp=None,
end_timestamp_op=None):
q = hbase_utils.make_query(alarm_id=alarm_id, q = hbase_utils.make_query(alarm_id=alarm_id,
on_behalf_of=on_behalf_of, type=alarm_type, 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( start_row, end_row = hbase_utils.make_timestamp_query(
hbase_utils.make_general_rowkey_scan, hbase_utils.make_general_rowkey_scan,
start=start_timestamp, start_op=start_timestamp_op, start=start_timestamp, start_op=start_timestamp_op,

View File

@ -34,7 +34,7 @@ class Connection(base.Connection):
def get_alarms(self, name=None, user=None, state=None, meter=None, def get_alarms(self, name=None, user=None, state=None, meter=None,
project=None, enabled=None, alarm_id=None, pagination=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.""" """Yields a lists of alarms that match filters."""
return [] return []

View File

@ -136,14 +136,15 @@ class Connection(base.Connection):
row.insufficient_data_actions), row.insufficient_data_actions),
rule=row.rule, rule=row.rule,
time_constraints=row.time_constraints, time_constraints=row.time_constraints,
repeat_actions=row.repeat_actions) repeat_actions=row.repeat_actions,
severity=row.severity)
def _retrieve_alarms(self, query): def _retrieve_alarms(self, query):
return (self._row_to_alarm_model(x) for x in query.all()) return (self._row_to_alarm_model(x) for x in query.all())
def get_alarms(self, name=None, user=None, state=None, meter=None, def get_alarms(self, name=None, user=None, state=None, meter=None,
project=None, enabled=None, alarm_id=None, pagination=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. """Yields a lists of alarms that match filters.
:param name: Optional name for alarm. :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 alarm_id: Optional alarm_id to return one alarm.
:param pagination: Optional pagination query. :param pagination: Optional pagination query.
:param alarm_type: Optional alarm type. :param alarm_type: Optional alarm type.
:param severity: Optional alarm severity
""" """
if pagination: if pagination:
@ -176,6 +178,8 @@ class Connection(base.Connection):
query = query.filter(models.Alarm.state == state) query = query.filter(models.Alarm.state == state)
if alarm_type is not None: if alarm_type is not None:
query = query.filter(models.Alarm.type == alarm_type) 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)) query = query.order_by(desc(models.Alarm.timestamp))
alarms = self._retrieve_alarms(query) alarms = self._retrieve_alarms(query)
@ -250,8 +254,9 @@ class Connection(base.Connection):
def get_alarm_changes(self, alarm_id, on_behalf_of, def get_alarm_changes(self, alarm_id, on_behalf_of,
user=None, project=None, alarm_type=None, user=None, project=None, alarm_type=None,
start_timestamp=None, start_timestamp_op=None, severity=None, start_timestamp=None,
end_timestamp=None, end_timestamp_op=None): start_timestamp_op=None, end_timestamp=None,
end_timestamp_op=None):
"""Yields list of AlarmChanges describing alarm history """Yields list of AlarmChanges describing alarm history
Changes are always sorted in reverse order of occurrence, given 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 user: Optional ID of user to return changes for
:param project: Optional ID of project to return changes for :param project: Optional ID of project to return changes for
:param alarm_type: Optional change type :param alarm_type: Optional change type
:param severity: Optional alarm severity
:param start_timestamp: Optional modified timestamp start range :param start_timestamp: Optional modified timestamp start range
:param start_timestamp_op: Optional timestamp start range operation :param start_timestamp_op: Optional timestamp start range operation
:param end_timestamp: Optional modified timestamp end range :param end_timestamp: Optional modified timestamp end range
@ -288,6 +294,8 @@ class Connection(base.Connection):
query = query.filter(models.AlarmChange.project_id == project) query = query.filter(models.AlarmChange.project_id == project)
if alarm_type is not None: if alarm_type is not None:
query = query.filter(models.AlarmChange.type == alarm_type) 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:
if start_timestamp_op == 'gt': if start_timestamp_op == 'gt':
query = query.filter( query = query.filter(

View File

@ -34,6 +34,10 @@ class Alarm(base.Model):
ALARM_ALARM: 'alarm_actions', ALARM_ALARM: 'alarm_actions',
} }
ALARM_LEVEL_LOW = 'low'
ALARM_LEVEL_MODERATE = 'moderate'
ALARM_LEVEL_CRITICAL = 'critical'
""" """
An alarm to monitor. An alarm to monitor.
@ -58,16 +62,16 @@ class Alarm(base.Model):
entering the insufficient data state entering the insufficient data state
:param repeat_actions: Is the actions should be triggered on each :param repeat_actions: Is the actions should be triggered on each
alarm evaluation. alarm evaluation.
:param severity: Alarm level (low/moderate/critical)
""" """
def __init__(self, alarm_id, type, enabled, name, description, def __init__(self, alarm_id, type, enabled, name, description,
timestamp, user_id, project_id, state, state_timestamp, timestamp, user_id, project_id, state, state_timestamp,
ok_actions, alarm_actions, insufficient_data_actions, 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): if not isinstance(timestamp, datetime.datetime):
raise TypeError(_("timestamp should be datetime object")) raise TypeError(_("timestamp should be datetime object"))
if not isinstance(state_timestamp, datetime.datetime): if not isinstance(state_timestamp, datetime.datetime):
raise TypeError(_("state_timestamp should be datetime object")) raise TypeError(_("state_timestamp should be datetime object"))
base.Model.__init__( base.Model.__init__(
self, self,
alarm_id=alarm_id, alarm_id=alarm_id,
@ -85,7 +89,8 @@ class Alarm(base.Model):
insufficient_data_actions=insufficient_data_actions, insufficient_data_actions=insufficient_data_actions,
repeat_actions=repeat_actions, repeat_actions=repeat_actions,
rule=rule, rule=rule,
time_constraints=time_constraints) time_constraints=time_constraints,
severity=severity)
class AlarmChange(base.Model): class AlarmChange(base.Model):
@ -94,6 +99,7 @@ class AlarmChange(base.Model):
:param event_id: UUID of the change event :param event_id: UUID of the change event
:param alarm_id: UUID of the alarm :param alarm_id: UUID of the alarm
:param type: The type of change :param type: The type of change
:param severity: The severity of alarm
:param detail: JSON fragment describing change :param detail: JSON fragment describing change
:param user_id: the user ID of the initiating identity :param user_id: the user ID of the initiating identity
:param project_id: the project 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, user_id,
project_id, project_id,
on_behalf_of, on_behalf_of,
severity=None,
timestamp=None timestamp=None
): ):
base.Model.__init__( base.Model.__init__(
@ -122,6 +129,7 @@ class AlarmChange(base.Model):
event_id=event_id, event_id=event_id,
alarm_id=alarm_id, alarm_id=alarm_id,
type=type, type=type,
severity=severity,
detail=detail, detail=detail,
user_id=user_id, user_id=user_id,
project_id=project_id, project_id=project_id,

View File

@ -81,7 +81,7 @@ class Connection(base.Connection):
def get_alarms(self, name=None, user=None, state=None, meter=None, def get_alarms(self, name=None, user=None, state=None, meter=None,
project=None, enabled=None, alarm_id=None, pagination=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. """Yields a lists of alarms that match filters.
:param name: Optional name for alarm. :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 alarm_id: Optional alarm_id to return one alarm.
:param pagination: Optional pagination query. :param pagination: Optional pagination query.
:param alarm_type: Optional alarm type. :param alarm_type: Optional alarm type.
:param severity: Optional alarm severity.
""" """
if pagination: if pagination:
raise ceilometer.NotImplementedError('Pagination not implemented') raise ceilometer.NotImplementedError('Pagination not implemented')
@ -114,6 +115,8 @@ class Connection(base.Connection):
q['rule.meter_name'] = meter q['rule.meter_name'] = meter
if alarm_type is not None: if alarm_type is not None:
q['type'] = alarm_type q['type'] = alarm_type
if severity is not None:
q['severity'] = severity
return self._retrieve_alarms(q, return self._retrieve_alarms(q,
[("timestamp", [("timestamp",
@ -122,8 +125,9 @@ class Connection(base.Connection):
def get_alarm_changes(self, alarm_id, on_behalf_of, def get_alarm_changes(self, alarm_id, on_behalf_of,
user=None, project=None, alarm_type=None, user=None, project=None, alarm_type=None,
start_timestamp=None, start_timestamp_op=None, severity=None, start_timestamp=None,
end_timestamp=None, end_timestamp_op=None): start_timestamp_op=None, end_timestamp=None,
end_timestamp_op=None):
"""Yields list of AlarmChanges describing alarm history """Yields list of AlarmChanges describing alarm history
Changes are always sorted in reverse order of occurrence, given 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 user: Optional ID of user to return changes for
:param project: Optional ID of project to return changes for :param project: Optional ID of project to return changes for
:param alarm_type: Optional change type :param alarm_type: Optional change type
:param severity: Optional change severity
:param start_timestamp: Optional modified timestamp start range :param start_timestamp: Optional modified timestamp start range
:param start_timestamp_op: Optional timestamp start range operation :param start_timestamp_op: Optional timestamp start range operation
:param end_timestamp: Optional modified timestamp end range :param end_timestamp: Optional modified timestamp end range
@ -156,6 +161,8 @@ class Connection(base.Connection):
q['project_id'] = project q['project_id'] = project
if alarm_type is not None: if alarm_type is not None:
q['type'] = alarm_type q['type'] = alarm_type
if severity is not None:
q['severity'] = severity
if start_timestamp or end_timestamp: if start_timestamp or end_timestamp:
ts_range = pymongo_utils.make_timestamp_range(start_timestamp, ts_range = pymongo_utils.make_timestamp_range(start_timestamp,
end_timestamp, end_timestamp,

View File

@ -86,6 +86,8 @@ state_kind = ["ok", "alarm", "insufficient data"]
state_kind_enum = wtypes.Enum(str, *state_kind) state_kind_enum = wtypes.Enum(str, *state_kind)
operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt') operation_kind = ('lt', 'le', 'eq', 'ne', 'ge', 'gt')
operation_kind_enum = wtypes.Enum(str, *operation_kind) 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): class ClientSideError(wsme.exc.ClientSideError):
@ -1831,6 +1833,10 @@ class Alarm(_Base):
state_timestamp = datetime.datetime state_timestamp = datetime.datetime
"The date of the last alarm state changed" "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): def __init__(self, rule=None, time_constraints=None, **kwargs):
super(Alarm, self).__init__(**kwargs) super(Alarm, self).__init__(**kwargs)
@ -1919,6 +1925,7 @@ class Alarm(_Base):
enabled=True, enabled=True,
timestamp=datetime.datetime.utcnow(), timestamp=datetime.datetime.utcnow(),
state="ok", state="ok",
severity="moderate",
state_timestamp=datetime.datetime.utcnow(), state_timestamp=datetime.datetime.utcnow(),
ok_actions=["http://site:8000/ok"], ok_actions=["http://site:8000/ok"],
alarm_actions=["http://site:8000/alarm"], alarm_actions=["http://site:8000/alarm"],
@ -2050,6 +2057,7 @@ class AlarmController(rest.RestController):
now = timeutils.utcnow() now = timeutils.utcnow()
data.alarm_id = self._id data.alarm_id = self._id
user, project = rbac.get_limited_to(pecan.request.headers) user, project = rbac.get_limited_to(pecan.request.headers)
if user: if user:
data.user_id = user data.user_id = user
@ -2064,7 +2072,7 @@ class AlarmController(rest.RestController):
data.state_timestamp = now data.state_timestamp = now
else: else:
data.state_timestamp = alarm_in.state_timestamp data.state_timestamp = alarm_in.state_timestamp
alarm_in.severity = data.severity
# make sure alarms are unique by name per project. # make sure alarms are unique by name per project.
if alarm_in.name != data.name: if alarm_in.name != data.name:
alarms = list(self.conn.get_alarms(name=data.name, alarms = list(self.conn.get_alarms(name=data.name,

View File

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

View File

@ -258,6 +258,7 @@ class Alarm(Base):
enabled = Column(Boolean) enabled = Column(Boolean)
name = Column(Text) name = Column(Text)
type = Column(String(50)) type = Column(String(50))
severity = Column(String(50))
description = Column(Text) description = Column(Text)
timestamp = Column(PreciseTimestamp, default=lambda: timeutils.utcnow()) timestamp = Column(PreciseTimestamp, default=lambda: timeutils.utcnow())

View File

@ -58,7 +58,8 @@ class TestEvaluate(base.TestEvaluatorBase):
'9cfc3e51-2ff1-4b1d-ac01-c1bd4c6d0d1e', '9cfc3e51-2ff1-4b1d-ac01-c1bd4c6d0d1e',
'1d441595-d069-4e05-95ab-8693ba6a8302'], '1d441595-d069-4e05-95ab-8693ba6a8302'],
operator='or', operator='or',
)), ),
severity='critical'),
models.Alarm(name='and-alarm', models.Alarm(name='and-alarm',
description='the and alarm', description='the and alarm',
type='combination', type='combination',
@ -79,7 +80,8 @@ class TestEvaluate(base.TestEvaluatorBase):
'b82734f4-9d06-48f3-8a86-fa59a0c99dc8', 'b82734f4-9d06-48f3-8a86-fa59a0c99dc8',
'15a700e5-2fe8-4b3d-8c55-9e92831f6a2b'], '15a700e5-2fe8-4b3d-8c55-9e92831f6a2b'],
operator='and', operator='and',
)) ),
severity='critical')
] ]
@staticmethod @staticmethod

View File

@ -65,7 +65,8 @@ class TestEvaluate(base.TestEvaluatorBase):
'value': 'cpu_util'}, 'value': 'cpu_util'},
{'field': 'resource_id', {'field': 'resource_id',
'op': 'eq', 'op': 'eq',
'value': 'my_instance'}]) 'value': 'my_instance'}]),
severity='critical'
), ),
models.Alarm(name='group_running_idle', models.Alarm(name='group_running_idle',
description='group_running_idle', description='group_running_idle',
@ -94,7 +95,8 @@ class TestEvaluate(base.TestEvaluatorBase):
'value': 'cpu_util'}, 'value': 'cpu_util'},
{'field': 'metadata.user_metadata.AS', {'field': 'metadata.user_metadata.AS',
'op': 'eq', 'op': 'eq',
'value': 'my_group'}]) 'value': 'my_group'}]),
severity='critical'
), ),
] ]

View File

@ -123,6 +123,7 @@ class TestCoordinate(tests_base.BaseTestCase):
alarm_actions=[], alarm_actions=[],
insufficient_data_actions=[], insufficient_data_actions=[],
alarm_id=uuid, alarm_id=uuid,
severity='critical',
time_constraints=[], time_constraints=[],
rule=dict( rule=dict(
statistic='avg', statistic='avg',

View File

@ -29,11 +29,12 @@ from ceilometer.tests import base as tests_base
DATA_JSON = jsonutils.loads( DATA_JSON = jsonutils.loads(
'{"current": "ALARM", "alarm_id": "foobar", "alarm_name": "testalarm",' '{"current": "ALARM", "alarm_id": "foobar", "alarm_name": "testalarm",'
' "reason": "what ?", "reason_data": {"test": "test"},' ' "severity": "critical", "reason": "what ?",'
' "previous": "OK"}' ' "reason_data": {"test": "test"}, "previous": "OK"}'
) )
NOTIFICATION = dict(alarm_id='foobar', NOTIFICATION = dict(alarm_id='foobar',
alarm_name='testalarm', alarm_name='testalarm',
severity='critical',
condition=dict(threshold=42), condition=dict(threshold=42),
reason='what ?', reason='what ?',
reason_data={'test': 'test'}, reason_data={'test': 'test'},
@ -65,6 +66,7 @@ class TestAlarmNotifier(tests_base.BaseTestCase):
'actions': ['test://'], 'actions': ['test://'],
'alarm_id': 'foobar', 'alarm_id': 'foobar',
'alarm_name': 'testalarm', 'alarm_name': 'testalarm',
'severity': 'critical',
'previous': 'OK', 'previous': 'OK',
'current': 'ALARM', 'current': 'ALARM',
'reason': 'Everything is on fire', 'reason': 'Everything is on fire',
@ -76,6 +78,7 @@ class TestAlarmNotifier(tests_base.BaseTestCase):
self.assertEqual((urlparse.urlsplit(data['actions'][0]), self.assertEqual((urlparse.urlsplit(data['actions'][0]),
data['alarm_id'], data['alarm_id'],
data['alarm_name'], data['alarm_name'],
data['severity'],
data['previous'], data['previous'],
data['current'], data['current'],
data['reason'], data['reason'],

View File

@ -67,6 +67,7 @@ class TestRPCAlarmNotifier(tests_base.BaseTestCase):
'project_id': 'snafu', 'project_id': 'snafu',
'period': 60, 'period': 60,
'alarm_id': str(uuid.uuid4()), 'alarm_id': str(uuid.uuid4()),
'severity': 'critical',
'matching_metadata':{'resource_id': 'matching_metadata':{'resource_id':
'my_instance'} 'my_instance'}
}), }),
@ -83,6 +84,7 @@ class TestRPCAlarmNotifier(tests_base.BaseTestCase):
'project_id': 'snafu', 'project_id': 'snafu',
'period': 300, 'period': 300,
'alarm_id': str(uuid.uuid4()), 'alarm_id': str(uuid.uuid4()),
'severity': 'critical',
'matching_metadata':{'metadata.user_metadata.AS': 'matching_metadata':{'metadata.user_metadata.AS':
'my_group'} 'my_group'}
}), }),
@ -109,6 +111,8 @@ class TestRPCAlarmNotifier(tests_base.BaseTestCase):
self.notifier_server.notified[i]["alarm_id"]) self.notifier_server.notified[i]["alarm_id"])
self.assertEqual(self.alarms[i].name, self.assertEqual(self.alarms[i].name,
self.notifier_server.notified[i]["alarm_name"]) self.notifier_server.notified[i]["alarm_name"])
self.assertEqual(self.alarms[i].severity,
self.notifier_server.notified[i]["severity"])
self.assertEqual(actions, self.assertEqual(actions,
self.notifier_server.notified[i]["actions"]) self.notifier_server.notified[i]["actions"])
self.assertEqual(previous[i], self.assertEqual(previous[i],

View File

@ -56,6 +56,7 @@ class TestAlarms(v2.FunctionalTest,
alarm_id='a', alarm_id='a',
description='a', description='a',
state='insufficient data', state='insufficient data',
severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
@ -76,7 +77,7 @@ class TestAlarms(v2.FunctionalTest,
query=[{'field': 'project_id', query=[{'field': 'project_id',
'op': 'eq', 'value': 'op': 'eq', 'value':
self.auth_headers['X-Project-Id']} self.auth_headers['X-Project-Id']}
]) ]),
), ),
models.Alarm(name='name2', models.Alarm(name='name2',
type='threshold', type='threshold',
@ -84,6 +85,7 @@ class TestAlarms(v2.FunctionalTest,
alarm_id='b', alarm_id='b',
description='b', description='b',
state='insufficient data', state='insufficient data',
severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
@ -102,7 +104,7 @@ class TestAlarms(v2.FunctionalTest,
query=[{'field': 'project_id', query=[{'field': 'project_id',
'op': 'eq', 'value': 'op': 'eq', 'value':
self.auth_headers['X-Project-Id']} self.auth_headers['X-Project-Id']}
]) ]),
), ),
models.Alarm(name='name3', models.Alarm(name='name3',
type='threshold', type='threshold',
@ -110,6 +112,7 @@ class TestAlarms(v2.FunctionalTest,
alarm_id='c', alarm_id='c',
description='c', description='c',
state='insufficient data', state='insufficient data',
severity='moderate',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
@ -128,7 +131,7 @@ class TestAlarms(v2.FunctionalTest,
query=[{'field': 'project_id', query=[{'field': 'project_id',
'op': 'eq', 'value': 'op': 'eq', 'value':
self.auth_headers['X-Project-Id']} self.auth_headers['X-Project-Id']}
]) ]),
), ),
models.Alarm(name='name4', models.Alarm(name='name4',
type='combination', type='combination',
@ -136,6 +139,7 @@ class TestAlarms(v2.FunctionalTest,
alarm_id='d', alarm_id='d',
description='d', description='d',
state='insufficient data', state='insufficient data',
severity='low',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
@ -146,7 +150,7 @@ class TestAlarms(v2.FunctionalTest,
project_id=self.auth_headers['X-Project-Id'], project_id=self.auth_headers['X-Project-Id'],
time_constraints=[], time_constraints=[],
rule=dict(alarm_ids=['a', 'b'], rule=dict(alarm_ids=['a', 'b'],
operator='or') operator='or'),
)]: )]:
self.alarm_conn.update_alarm(alarm) self.alarm_conn.update_alarm(alarm)
@ -222,7 +226,8 @@ class TestAlarms(v2.FunctionalTest,
user_id=self.auth_headers['X-User-Id'], user_id=self.auth_headers['X-User-Id'],
project_id=self.auth_headers['X-Project-Id'], project_id=self.auth_headers['X-Project-Id'],
time_constraints=[], time_constraints=[],
rule=dict(alarm_ids=['a', 'b'], operator='or')) rule=dict(alarm_ids=['a', 'b'], operator='or'),
severity='critical')
self.alarm_conn.update_alarm(alarm) self.alarm_conn.update_alarm(alarm)
resp = self.get_json('/alarms', resp = self.get_json('/alarms',
q=[{'field': 'state', q=[{'field': 'state',
@ -281,7 +286,8 @@ class TestAlarms(v2.FunctionalTest,
user_id=self.auth_headers['X-User-Id'], user_id=self.auth_headers['X-User-Id'],
project_id=self.auth_headers['X-Project-Id'], project_id=self.auth_headers['X-Project-Id'],
time_constraints=[], time_constraints=[],
rule=dict(alarm_ids=['a', 'b'], operator='or')) rule=dict(alarm_ids=['a', 'b'], operator='or'),
severity='critical')
self.alarm_conn.update_alarm(alarm) self.alarm_conn.update_alarm(alarm)
alarms = self.get_json('/alarms', alarms = self.get_json('/alarms',
@ -570,6 +576,27 @@ class TestAlarms(v2.FunctionalTest,
alarms = list(self.alarm_conn.get_alarms()) alarms = list(self.alarm_conn.get_alarms())
self.assertEqual(4, len(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): def test_post_invalid_alarm_input_comparison_operator(self):
json = { json = {
'name': 'alarm2', 'name': 'alarm2',
@ -928,6 +955,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'added_alarm', 'name': 'added_alarm',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'low',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -975,6 +1003,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'added_alarm', 'name': 'added_alarm',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'low',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -1543,6 +1572,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'name_put', 'name': 'name_put',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'critical',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -1585,6 +1615,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'name_put', 'name': 'name_put',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'critical',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -1634,6 +1665,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'name1', 'name': 'name1',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'critical',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -1670,6 +1702,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'name1', 'name': 'name1',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'critical',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -1708,6 +1741,7 @@ class TestAlarms(v2.FunctionalTest,
'name': 'name1', 'name': 'name1',
'state': 'ok', 'state': 'ok',
'type': 'threshold', 'type': 'threshold',
'severity': 'critical',
'ok_actions': ['spam://something/ok'], 'ok_actions': ['spam://something/ok'],
'alarm_actions': ['http://something/alarm'], 'alarm_actions': ['http://something/alarm'],
'insufficient_data_actions': ['http://something/no'], 'insufficient_data_actions': ['http://something/no'],
@ -2158,10 +2192,11 @@ class TestAlarms(v2.FunctionalTest,
query = dict(field='alarm_id', op='eq', value='b') query = dict(field='alarm_id', op='eq', value='b')
resp = self._get_alarm_history(alarm, query=query, resp = self._get_alarm_history(alarm, query=query,
expect_errors=True, status=400) expect_errors=True, status=400)
self.assertEqual('Unknown argument: "alarm_id": unrecognized' self.assertEqual(u'Unknown argument: "alarm_id": unrecognized'
" field in query: [<Query u'alarm_id' eq" " field in query: [<Query u'alarm_id' eq"
" u'b' Unset>], valid keys: ['project', " " u'b' Unset>], valid keys: ['project', "
"'search_offset', 'timestamp', 'type', 'user']", "'search_offset', 'severity', 'timestamp',"
" 'type', 'user']",
resp.json['error_message']['faultstring']) resp.json['error_message']['faultstring'])
def test_get_alarm_history_constrained_by_not_supported_rule(self): 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') query = dict(field='abcd', op='eq', value='abcd')
resp = self._get_alarm_history(alarm, query=query, resp = self._get_alarm_history(alarm, query=query,
expect_errors=True, status=400) expect_errors=True, status=400)
self.assertEqual('Unknown argument: "abcd": unrecognized' self.assertEqual(u'Unknown argument: "abcd": unrecognized'
" field in query: [<Query u'abcd' eq" " field in query: [<Query u'abcd' eq"
" u'abcd' Unset>], valid keys: ['project', " " u'abcd' Unset>], valid keys: ['project', "
"'search_offset', 'timestamp', 'type', 'user']", "'search_offset', 'severity', 'timestamp',"
" 'type', 'user']",
resp.json['error_message']['faultstring']) resp.json['error_message']['faultstring'])
def test_get_nonexistent_alarm_history(self): def test_get_nonexistent_alarm_history(self):
@ -2186,6 +2222,7 @@ class TestAlarms(v2.FunctionalTest,
json = { json = {
'name': 'sent_notification', 'name': 'sent_notification',
'type': 'threshold', 'type': 'threshold',
'severity': 'low',
'threshold_rule': { 'threshold_rule': {
'meter_name': 'ameter', 'meter_name': 'ameter',
'comparison_operator': 'gt', 'comparison_operator': 'gt',

View File

@ -351,7 +351,8 @@ class TestQueryAlarmsController(tests_api.FunctionalTest,
'project_id', 'project_id',
'op': 'eq', 'op': 'eq',
'value': 'value':
project_id}])) project_id}]),
severity='critical')
self.alarm_conn.update_alarm(alarm) self.alarm_conn.update_alarm(alarm)
def test_query_all(self): def test_query_all(self):

View File

@ -345,7 +345,7 @@ class TestQueryToKwArgs(tests_base.BaseTestCase):
api._query_to_kwargs, q, api._query_to_kwargs, q,
alarm_storage_base.Connection.get_alarm_changes) alarm_storage_base.Connection.get_alarm_changes)
valid_keys = ['alarm_id', 'on_behalf_of', 'project', 'search_offset', valid_keys = ['alarm_id', 'on_behalf_of', 'project', 'search_offset',
'timestamp', 'type', 'user'] 'severity', 'timestamp', 'type', 'user']
msg = ("unrecognized field in query: %s, " msg = ("unrecognized field in query: %s, "
"valid keys: %s") % (q, valid_keys) "valid keys: %s") % (q, valid_keys)
expected_exc = wsme.exc.UnknownArgument('abc', msg) expected_exc = wsme.exc.UnknownArgument('abc', msg)
@ -402,7 +402,7 @@ class TestQueryToKwArgs(tests_base.BaseTestCase):
wsme.exc.UnknownArgument, wsme.exc.UnknownArgument,
api._query_to_kwargs, q, alarm_storage_base.Connection.get_alarms) api._query_to_kwargs, q, alarm_storage_base.Connection.get_alarms)
valid_keys = ['alarm_id', 'enabled', 'meter', 'name', 'pagination', valid_keys = ['alarm_id', 'enabled', 'meter', 'name', 'pagination',
'project', 'state', 'type', 'user'] 'project', 'severity', 'state', 'type', 'user']
msg = ("unrecognized field in query: %s, " msg = ("unrecognized field in query: %s, "
"valid keys: %s") % (q, valid_keys) "valid keys: %s") % (q, valid_keys)
expected_exc = wsme.exc.UnknownArgument('abc', msg) expected_exc = wsme.exc.UnknownArgument('abc', msg)

View File

@ -77,15 +77,15 @@ class ModelTest(testbase.BaseTestCase):
"timestamp", "user_id", "project_id", "state", "timestamp", "user_id", "project_id", "state",
"state_timestamp", "ok_actions", "alarm_actions", "state_timestamp", "ok_actions", "alarm_actions",
"insufficient_data_actions", "repeat_actions", "rule", "insufficient_data_actions", "repeat_actions", "rule",
"time_constraints"] "severity", "time_constraints"]
self.assertEqual(set(alarm_fields), self.assertEqual(set(alarm_fields),
set(alarm_models.Alarm.get_field_names())) set(alarm_models.Alarm.get_field_names()))
def test_get_field_names_of_alarmchange(self): def test_get_field_names_of_alarmchange(self):
alarmchange_fields = ["event_id", "alarm_id", "type", "detail", alarmchange_fields = ["event_id", "alarm_id", "type", "detail",
"user_id", "project_id", "on_behalf_of", "user_id", "project_id", "severity",
"timestamp"] "on_behalf_of", "timestamp"]
self.assertEqual(set(alarmchange_fields), self.assertEqual(set(alarmchange_fields),
set(alarm_models.AlarmChange.get_field_names())) set(alarm_models.AlarmChange.get_field_names()))