Expose alarm state reason to API

We currently show no reason from the API point of view
of why we don't have enough data to evaluate alarms or why
we change the state of the alarm.

This change exposes the reason we known to the API.

Change-Id: Ic1fe95090339d39ad9638654db815aee41a7921e
This commit is contained in:
Mehdi Abaakouk 2017-06-04 18:30:32 +02:00
parent 343742cecf
commit dfa63a5e4e
14 changed files with 154 additions and 5 deletions

View File

@ -74,6 +74,9 @@ state_kind_enum = wtypes.Enum(str, *state_kind)
severity_kind = ["low", "moderate", "critical"] severity_kind = ["low", "moderate", "critical"]
severity_kind_enum = wtypes.Enum(str, *severity_kind) severity_kind_enum = wtypes.Enum(str, *severity_kind)
ALARM_REASON_DEFAULT = "Not evaluated yet"
ALARM_REASON_MANUAL = "Manually set via API"
class OverQuota(base.ClientSideError): class OverQuota(base.ClientSideError):
def __init__(self, data): def __init__(self, data):
@ -250,6 +253,9 @@ class Alarm(base.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"
state_reason = wsme.wsattr(wtypes.text, default=ALARM_REASON_DEFAULT)
"The reason of the current state"
severity = base.AdvEnum('severity', str, *severity_kind, severity = base.AdvEnum('severity', str, *severity_kind,
default='low') default='low')
"The severity of the alarm" "The severity of the alarm"
@ -359,6 +365,7 @@ class Alarm(base.Base):
timestamp=datetime.datetime(2015, 1, 1, 12, 0, 0, 0), timestamp=datetime.datetime(2015, 1, 1, 12, 0, 0, 0),
state="ok", state="ok",
severity="moderate", severity="moderate",
state_reason="threshold over 90%",
state_timestamp=datetime.datetime(2015, 1, 1, 12, 0, 0, 0), state_timestamp=datetime.datetime(2015, 1, 1, 12, 0, 0, 0),
ok_actions=["http://site:8000/ok"], ok_actions=["http://site:8000/ok"],
alarm_actions=["http://site:8000/alarm"], alarm_actions=["http://site:8000/alarm"],
@ -620,8 +627,10 @@ class AlarmController(rest.RestController):
data.timestamp = now data.timestamp = now
if alarm_in.state != data.state: if alarm_in.state != data.state:
data.state_timestamp = now data.state_timestamp = now
data.state_reason = ALARM_REASON_MANUAL
else: else:
data.state_timestamp = alarm_in.state_timestamp data.state_timestamp = alarm_in.state_timestamp
data.state_reason = alarm_in.state_reason
ALARMS_RULES[data.type].plugin.update_hook(data) ALARMS_RULES[data.type].plugin.update_hook(data)
@ -699,8 +708,10 @@ class AlarmController(rest.RestController):
now = timeutils.utcnow() now = timeutils.utcnow()
alarm.state = state alarm.state = state
alarm.state_timestamp = now alarm.state_timestamp = now
alarm.state_reason = ALARM_REASON_MANUAL
alarm = pecan.request.storage.update_alarm(alarm) alarm = pecan.request.storage.update_alarm(alarm)
change = {'state': alarm.state} change = {'state': alarm.state,
'state_reason': alarm.state_reason}
self._record_change(change, now, on_behalf_of=alarm.project_id, self._record_change(change, now, on_behalf_of=alarm.project_id,
type=models.AlarmChange.STATE_TRANSITION) type=models.AlarmChange.STATE_TRANSITION)
return alarm.state return alarm.state
@ -785,6 +796,7 @@ class AlarmsController(rest.RestController):
data.timestamp = now data.timestamp = now
data.state_timestamp = now data.state_timestamp = now
data.state_reason = ALARM_REASON_DEFAULT
ALARMS_RULES[data.type].plugin.create_hook(data) ALARMS_RULES[data.type].plugin.create_hook(data)

View File

@ -116,6 +116,7 @@ class Evaluator(object):
try: try:
previous = alarm.state previous = alarm.state
alarm.state = state alarm.state = state
alarm.state_reason = reason
if previous != state or always_record: if previous != state or always_record:
LOG.info('alarm %(id)s transitioning to %(state)s because ' LOG.info('alarm %(id)s transitioning to %(state)s because '
'%(reason)s', {'id': alarm.alarm_id, '%(reason)s', {'id': alarm.alarm_id,

View File

@ -147,6 +147,7 @@ class Connection(base.Connection):
project_id=row.project_id, project_id=row.project_id,
state=row.state, state=row.state,
state_timestamp=row.state_timestamp, state_timestamp=row.state_timestamp,
state_reason=row.state_reason,
ok_actions=row.ok_actions, ok_actions=row.ok_actions,
alarm_actions=row.alarm_actions, alarm_actions=row.alarm_actions,
insufficient_data_actions=( insufficient_data_actions=(

View File

@ -51,6 +51,7 @@ class Alarm(base.Model):
:param description: User friendly description of the alarm :param description: User friendly description of the alarm
:param enabled: Is the alarm enabled :param enabled: Is the alarm enabled
:param state: Alarm state (ok/alarm/insufficient data) :param state: Alarm state (ok/alarm/insufficient data)
:param state_reason: Alarm state reason
:param rule: A rule that defines when the alarm fires :param rule: A rule that defines when the alarm fires
:param user_id: the owner/creator of the alarm :param user_id: the owner/creator of the alarm
:param project_id: the project_id of the creator :param project_id: the project_id of the creator
@ -70,8 +71,9 @@ class Alarm(base.Model):
""" """
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, state_reason, ok_actions, alarm_actions,
repeat_actions, rule, time_constraints, severity=None): insufficient_data_actions, 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):
@ -88,6 +90,7 @@ class Alarm(base.Model):
project_id=project_id, project_id=project_id,
state=state, state=state,
state_timestamp=state_timestamp, state_timestamp=state_timestamp,
state_reason=state_reason,
ok_actions=ok_actions, ok_actions=ok_actions,
alarm_actions=alarm_actions, alarm_actions=alarm_actions,
insufficient_data_actions=insufficient_data_actions, insufficient_data_actions=insufficient_data_actions,

View File

@ -0,0 +1,37 @@
# -*- encoding: utf-8 -*-
#
# Copyright 2017 OpenStack Foundation
#
# 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.
#
"""add_reason_column
Revision ID: 6ae0d05d9451
Revises: 367aadf5485f
Create Date: 2017-06-05 16:42:42.379029
"""
# revision identifiers, used by Alembic.
revision = '6ae0d05d9451'
down_revision = '367aadf5485f'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('alarm', sa.Column('state_reason', sa.Text, nullable=True))

View File

@ -94,6 +94,7 @@ class Alarm(Base):
project_id = Column(String(128)) project_id = Column(String(128))
state = Column(String(255)) state = Column(String(255))
state_reason = Column(Text)
state_timestamp = Column(TimestampUTC, state_timestamp = Column(TimestampUTC,
default=lambda: timeutils.utcnow()) default=lambda: timeutils.utcnow())

View File

@ -38,6 +38,7 @@ def default_alarms(auth_headers):
alarm_id='a', alarm_id='a',
description='a', description='a',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -67,6 +68,7 @@ def default_alarms(auth_headers):
alarm_id='b', alarm_id='b',
description='b', description='b',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -94,6 +96,7 @@ def default_alarms(auth_headers):
alarm_id='c', alarm_id='c',
description='c', description='c',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
severity='moderate', severity='moderate',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -221,6 +224,7 @@ class TestAlarms(TestAlarmsBase):
alarm_id='c', alarm_id='c',
description='c', description='c',
state='ok', state='ok',
state_reason='Not evaluated',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
@ -298,6 +302,7 @@ class TestAlarms(TestAlarmsBase):
alarm_id='c', alarm_id='c',
description='c', description='c',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
@ -809,6 +814,7 @@ class TestAlarms(TestAlarmsBase):
'enabled': False, 'enabled': False,
'name': 'added_alarm', 'name': 'added_alarm',
'state': 'ok', 'state': 'ok',
'state_reason': 'ignored',
'type': 'threshold', 'type': 'threshold',
'severity': 'low', 'severity': 'low',
'ok_actions': ['http://something/ok'], 'ok_actions': ['http://something/ok'],
@ -841,6 +847,8 @@ class TestAlarms(TestAlarmsBase):
# to check to IntegerType type conversion # to check to IntegerType type conversion
json['threshold_rule']['evaluation_periods'] = 3 json['threshold_rule']['evaluation_periods'] = 3
json['threshold_rule']['period'] = 180 json['threshold_rule']['period'] = 180
# to check it's read only
json['state_reason'] = "Not evaluated yet"
self._verify_alarm(json, alarms[0], 'added_alarm') self._verify_alarm(json, alarms[0], 'added_alarm')
def test_post_alarm_outlier_exclusion_set(self): def test_post_alarm_outlier_exclusion_set(self):
@ -1289,6 +1297,58 @@ class TestAlarms(TestAlarmsBase):
self.assertEqual(['test://', 'log://'], self.assertEqual(['test://', 'log://'],
alarms[0].alarm_actions) alarms[0].alarm_actions)
def test_exercise_state_reason(self):
body = {
'name': 'nostate',
'type': 'threshold',
'threshold_rule': {
'meter_name': 'ameter',
'query': [{'field': 'metadata.field',
'op': 'eq',
'value': '5',
'type': 'string'}],
'comparison_operator': 'le',
'statistic': 'count',
'threshold': 50,
'evaluation_periods': '3',
'period': '180',
},
}
headers = self.auth_headers
headers['X-Roles'] = 'admin'
self.post_json('/alarms', params=body, status=201,
headers=headers)
alarms = list(self.alarm_conn.get_alarms(name='nostate'))
self.assertEqual(1, len(alarms))
alarm_id = alarms[0].alarm_id
alarm = self._get_alarm(alarm_id)
self.assertEqual("insufficient data", alarm['state'])
self.assertEqual("Not evaluated yet", alarm['state_reason'])
# Ensure state reason is updated
alarm = self._get_alarm('a')
alarm['state'] = 'ok'
self.put_json('/alarms/%s' % alarm_id,
params=alarm,
headers=self.auth_headers)
alarm = self._get_alarm(alarm_id)
self.assertEqual("ok", alarm['state'])
self.assertEqual("Manually set via API", alarm['state_reason'])
# Ensure state reason read only
alarm = self._get_alarm('a')
alarm['state'] = 'alarm'
alarm['state_reason'] = 'oh no!'
self.put_json('/alarms/%s' % alarm_id,
params=alarm,
headers=self.auth_headers)
alarm = self._get_alarm(alarm_id)
self.assertEqual("alarm", alarm['state'])
self.assertEqual("Manually set via API", alarm['state_reason'])
def test_post_alarm_without_actions(self): def test_post_alarm_without_actions(self):
body = { body = {
'name': 'alarm_actions_none', 'name': 'alarm_actions_none',
@ -1641,6 +1701,8 @@ class TestAlarms(TestAlarmsBase):
alarms = list(self.alarm_conn.get_alarms(alarm_id=data[0]['alarm_id'])) alarms = list(self.alarm_conn.get_alarms(alarm_id=data[0]['alarm_id']))
self.assertEqual(1, len(alarms)) self.assertEqual(1, len(alarms))
self.assertEqual('alarm', alarms[0].state) self.assertEqual('alarm', alarms[0].state)
self.assertEqual('Manually set via API',
alarms[0].state_reason)
self.assertEqual('alarm', resp.json) self.assertEqual('alarm', resp.json)
def test_set_invalid_state_alarm(self): def test_set_invalid_state_alarm(self):
@ -1726,6 +1788,7 @@ class TestAlarmsHistory(TestAlarmsBase):
alarm_id='a', alarm_id='a',
description='a', description='a',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -1764,7 +1827,10 @@ class TestAlarmsHistory(TestAlarmsBase):
def _assert_is_subset(self, expected, actual): def _assert_is_subset(self, expected, actual):
for k, v in six.iteritems(expected): for k, v in six.iteritems(expected):
self.assertEqual(v, actual.get(k), 'mismatched field: %s' % k) current = actual.get(k)
if k == 'detail' and isinstance(v, dict):
current = jsonutils.loads(current)
self.assertEqual(v, current, 'mismatched field: %s' % k)
self.assertIsNotNone(actual['event_id']) self.assertIsNotNone(actual['event_id'])
def _assert_in_json(self, expected, actual): def _assert_in_json(self, expected, actual):
@ -1955,7 +2021,9 @@ class TestAlarmsHistory(TestAlarmsBase):
auth_headers=auth) auth_headers=auth)
self.assertEqual(2, len(history), 'hist: %s' % history) self.assertEqual(2, len(history), 'hist: %s' % history)
self._assert_is_subset(dict(alarm_id=alarm['alarm_id'], self._assert_is_subset(dict(alarm_id=alarm['alarm_id'],
detail='{"state": "alarm"}', detail={"state": "alarm",
"state_reason":
"Manually set via API"},
on_behalf_of=alarm['project_id'], on_behalf_of=alarm['project_id'],
project_id=admin_project, project_id=admin_project,
type='rule change', type='rule change',
@ -2405,6 +2473,7 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase):
alarm_id='e', alarm_id='e',
description='e', description='e',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -2432,6 +2501,7 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase):
alarm_id='f', alarm_id='f',
description='f', description='f',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -2458,6 +2528,7 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase):
alarm_id='g', alarm_id='g',
description='f', description='f',
state='insufficient data', state='insufficient data',
state_reason='Not evaluated',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -2671,6 +2742,7 @@ class TestAlarmsEvent(TestAlarmsBase):
alarm_id='h', alarm_id='h',
description='h', description='h',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
severity='moderate', severity='moderate',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
@ -2796,6 +2868,7 @@ class TestAlarmsCompositeRule(TestAlarmsBase):
alarm_id='composite', alarm_id='composite',
description='composite', description='composite',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
severity='moderate', severity='moderate',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,

View File

@ -49,6 +49,7 @@ class TestQueryAlarmsController(tests_api.FunctionalTest):
alarm_id=alarm_id, alarm_id=alarm_id,
description='a', description='a',
state=state, state=state,
state_reason="state_reason",
state_timestamp=date, state_timestamp=date,
timestamp=date, timestamp=date,
ok_actions=[], ok_actions=[],

View File

@ -56,6 +56,7 @@ class AlarmTestBase(DBTestBase):
user_id='me', user_id='me',
project_id='and-da-boys', project_id='and-da-boys',
state="insufficient data", state="insufficient data",
state_reason="insufficient data",
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
alarm_actions=['http://nowhere/alarms'], alarm_actions=['http://nowhere/alarms'],
@ -85,6 +86,7 @@ class AlarmTestBase(DBTestBase):
user_id='me', user_id='me',
project_id='and-da-boys', project_id='and-da-boys',
state="insufficient data", state="insufficient data",
state_reason="insufficient data",
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
alarm_actions=['http://nowhere/alarms'], alarm_actions=['http://nowhere/alarms'],
@ -112,6 +114,7 @@ class AlarmTestBase(DBTestBase):
user_id='me', user_id='me',
project_id='and-da-boys', project_id='and-da-boys',
state="insufficient data", state="insufficient data",
state_reason="insufficient data",
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
alarm_actions=['http://nowhere/alarms'], alarm_actions=['http://nowhere/alarms'],
@ -219,6 +222,7 @@ class AlarmTest(AlarmTestBase):
user_id='bla', user_id='bla',
project_id='ffo', project_id='ffo',
state="insufficient data", state="insufficient data",
state_reason="insufficient data",
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
ok_actions=[], ok_actions=[],
alarm_actions=[], alarm_actions=[],

View File

@ -170,6 +170,7 @@ class CompositeTest(BaseCompositeEvaluate):
project_id='fake_project', project_id='fake_project',
alarm_id=uuidutils.generate_uuid(), alarm_id=uuidutils.generate_uuid(),
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
@ -190,6 +191,7 @@ class CompositeTest(BaseCompositeEvaluate):
user_id='fake_user', user_id='fake_user',
project_id='fake_project', project_id='fake_project',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
@ -211,6 +213,7 @@ class CompositeTest(BaseCompositeEvaluate):
user_id='fake_user', user_id='fake_user',
project_id='fake_project', project_id='fake_project',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
@ -233,6 +236,7 @@ class CompositeTest(BaseCompositeEvaluate):
project_id='fake_project', project_id='fake_project',
alarm_id=uuidutils.generate_uuid(), alarm_id=uuidutils.generate_uuid(),
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
@ -456,6 +460,7 @@ class OtherCompositeTest(BaseCompositeEvaluate):
user_id='fake_user', user_id='fake_user',
project_id='fake_project', project_id='fake_project',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=['log://'], insufficient_data_actions=['log://'],

View File

@ -41,6 +41,7 @@ class TestEventAlarmEvaluate(base.TestEvaluatorBase):
alarm_id=alarm_id, alarm_id=alarm_id,
description='desc', description='desc',
state=kwargs.get('state', 'insufficient data'), state=kwargs.get('state', 'insufficient data'),
state_reason='reason',
severity='critical', severity='critical',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,

View File

@ -45,6 +45,7 @@ class TestGnocchiEvaluatorBase(base.TestEvaluatorBase):
project_id='snafu', project_id='snafu',
alarm_id=uuidutils.generate_uuid(), alarm_id=uuidutils.generate_uuid(),
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
@ -69,6 +70,7 @@ class TestGnocchiEvaluatorBase(base.TestEvaluatorBase):
user_id='foobar', user_id='foobar',
project_id='snafu', project_id='snafu',
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
@ -94,6 +96,7 @@ class TestGnocchiEvaluatorBase(base.TestEvaluatorBase):
project_id='snafu', project_id='snafu',
alarm_id=uuidutils.generate_uuid(), alarm_id=uuidutils.generate_uuid(),
state='insufficient data', state='insufficient data',
state_reason='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],

View File

@ -47,6 +47,7 @@ class TestEvaluate(base.TestEvaluatorBase):
alarm_id=uuidutils.generate_uuid(), alarm_id=uuidutils.generate_uuid(),
state='insufficient data', state='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
state_reason='Not evaluated',
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
ok_actions=[], ok_actions=[],
@ -76,6 +77,7 @@ class TestEvaluate(base.TestEvaluatorBase):
project_id='snafu', project_id='snafu',
state='insufficient data', state='insufficient data',
state_timestamp=constants.MIN_DATETIME, state_timestamp=constants.MIN_DATETIME,
state_reason='Not evaluated',
timestamp=constants.MIN_DATETIME, timestamp=constants.MIN_DATETIME,
insufficient_data_actions=[], insufficient_data_actions=[],
ok_actions=[], ok_actions=[],
@ -419,6 +421,7 @@ class TestEvaluate(base.TestEvaluatorBase):
primitive_alarms = [a.as_dict() for a in self.alarms] primitive_alarms = [a.as_dict() for a in self.alarms]
for alarm in original_alarms: for alarm in original_alarms:
alarm.state = 'alarm' alarm.state = 'alarm'
alarm.state_reason = mock.ANY
primitive_original_alarms = [a.as_dict() for a in original_alarms] primitive_original_alarms = [a.as_dict() for a in original_alarms]
self.assertEqual(primitive_original_alarms, primitive_alarms) self.assertEqual(primitive_original_alarms, primitive_alarms)

View File

@ -0,0 +1,4 @@
---
features:
- |
The reason of the state change is now part of the API as "state_reason" field of the alarm object.