Merge "Adds time constraints to alarms"
This commit is contained in:
commit
3d10fb7aa4
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import croniter
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
|
||||||
from ceilometerclient import client as ceiloclient
|
from ceilometerclient import client as ceiloclient
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
@ -25,6 +28,7 @@ import six
|
|||||||
|
|
||||||
from ceilometer.openstack.common.gettextutils import _ # noqa
|
from ceilometer.openstack.common.gettextutils import _ # noqa
|
||||||
from ceilometer.openstack.common import log
|
from ceilometer.openstack.common import log
|
||||||
|
from ceilometer.openstack.common import timeutils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -77,6 +81,39 @@ class Evaluator(object):
|
|||||||
# cycle (unless alarm state reverts in the meantime)
|
# cycle (unless alarm state reverts in the meantime)
|
||||||
LOG.exception(_('alarm state update failed'))
|
LOG.exception(_('alarm state update failed'))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def within_time_constraint(cls, alarm):
|
||||||
|
"""Check whether the alarm is within at least one of its time
|
||||||
|
constraints. If there are none, then the answer is yes.
|
||||||
|
"""
|
||||||
|
if not alarm.time_constraints:
|
||||||
|
return True
|
||||||
|
|
||||||
|
now_utc = timeutils.utcnow()
|
||||||
|
for tc in alarm.time_constraints:
|
||||||
|
tz = pytz.timezone(tc['timezone']) if tc['timezone'] else None
|
||||||
|
now_tz = now_utc.astimezone(tz) if tz else now_utc
|
||||||
|
start_cron = croniter.croniter(tc['start'], now_tz)
|
||||||
|
if cls._is_exact_match(start_cron, now_tz):
|
||||||
|
return True
|
||||||
|
latest_start = start_cron.get_prev(datetime.datetime)
|
||||||
|
duration = datetime.timedelta(seconds=tc['duration'])
|
||||||
|
if latest_start <= now_tz <= latest_start + duration:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_exact_match(cron, ts):
|
||||||
|
"""Handle edge case where if the timestamp is the same as the
|
||||||
|
cron point in time to the minute, croniter returns the previous
|
||||||
|
start, not the current. We can check this by first going one
|
||||||
|
step back and then one step forward and check if we are
|
||||||
|
at the original point in time.
|
||||||
|
"""
|
||||||
|
cron.get_prev()
|
||||||
|
diff = timeutils.total_seconds(ts - cron.get_next(datetime.datetime))
|
||||||
|
return abs(diff) < 60 # minute precision
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def evaluate(self, alarm):
|
def evaluate(self, alarm):
|
||||||
'''interface definition
|
'''interface definition
|
||||||
|
@ -96,6 +96,11 @@ class CombinationEvaluator(evaluator.Evaluator):
|
|||||||
self._refresh(alarm, state, reason, reason_data)
|
self._refresh(alarm, state, reason, reason_data)
|
||||||
|
|
||||||
def evaluate(self, alarm):
|
def evaluate(self, alarm):
|
||||||
|
if not self.within_time_constraint(alarm):
|
||||||
|
LOG.debug(_('Attempted to evaluate alarm %s, but it is not '
|
||||||
|
'within its time constraint.') % alarm.alarm_id)
|
||||||
|
return
|
||||||
|
|
||||||
states = zip(alarm.rule['alarm_ids'],
|
states = zip(alarm.rule['alarm_ids'],
|
||||||
itertools.imap(self._get_alarm_state,
|
itertools.imap(self._get_alarm_state,
|
||||||
alarm.rule['alarm_ids']))
|
alarm.rule['alarm_ids']))
|
||||||
|
@ -173,6 +173,11 @@ class ThresholdEvaluator(evaluator.Evaluator):
|
|||||||
self._refresh(alarm, state, reason, reason_data)
|
self._refresh(alarm, state, reason, reason_data)
|
||||||
|
|
||||||
def evaluate(self, alarm):
|
def evaluate(self, alarm):
|
||||||
|
if not self.within_time_constraint(alarm):
|
||||||
|
LOG.debug(_('Attempted to evaluate alarm %s, but it is not '
|
||||||
|
'within its time constraint.') % alarm.alarm_id)
|
||||||
|
return
|
||||||
|
|
||||||
query = self._bound_duration(
|
query = self._bound_duration(
|
||||||
alarm,
|
alarm,
|
||||||
alarm.rule['query']
|
alarm.rule['query']
|
||||||
|
@ -28,11 +28,13 @@
|
|||||||
import ast
|
import ast
|
||||||
import base64
|
import base64
|
||||||
import copy
|
import copy
|
||||||
|
import croniter
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
import pytz
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
@ -108,6 +110,18 @@ class AdvEnum(wtypes.wsproperty):
|
|||||||
setattr(parent, self._name, value)
|
setattr(parent, self._name, value)
|
||||||
|
|
||||||
|
|
||||||
|
class CronType(wtypes.UserType):
|
||||||
|
"""A user type that represents a cron format."""
|
||||||
|
basetype = six.string_types
|
||||||
|
name = 'cron'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(value):
|
||||||
|
# raises ValueError if invalid
|
||||||
|
croniter.croniter(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class _Base(wtypes.Base):
|
class _Base(wtypes.Base):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -1505,6 +1519,59 @@ class AlarmCombinationRule(_Base):
|
|||||||
'153462d0-a9b8-4b5b-8175-9e4b05e9b856'])
|
'153462d0-a9b8-4b5b-8175-9e4b05e9b856'])
|
||||||
|
|
||||||
|
|
||||||
|
class AlarmTimeConstraint(_Base):
|
||||||
|
"""Representation of a time constraint on an alarm."""
|
||||||
|
|
||||||
|
name = wsme.wsattr(wtypes.text, mandatory=True)
|
||||||
|
"The name of the constraint"
|
||||||
|
|
||||||
|
_description = None # provide a default
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
if not self._description:
|
||||||
|
return 'Time constraint at %s lasting for %s seconds' \
|
||||||
|
% (self.start, self.duration)
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
def set_description(self, value):
|
||||||
|
self._description = value
|
||||||
|
|
||||||
|
description = wsme.wsproperty(wtypes.text, get_description,
|
||||||
|
set_description)
|
||||||
|
"The description of the constraint"
|
||||||
|
|
||||||
|
start = wsme.wsattr(CronType(), mandatory=True)
|
||||||
|
"Start point of the time constraint, in cron format"
|
||||||
|
|
||||||
|
duration = wsme.wsattr(wtypes.IntegerType(minimum=0), mandatory=True)
|
||||||
|
"How long the constraint should last, in seconds"
|
||||||
|
|
||||||
|
timezone = wsme.wsattr(wtypes.text, default="")
|
||||||
|
"Timezone of the constraint"
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return self.as_dict_from_keys(['name', 'description', 'start',
|
||||||
|
'duration', 'timezone'])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(tc):
|
||||||
|
if tc.timezone:
|
||||||
|
try:
|
||||||
|
pytz.timezone(tc.timezone)
|
||||||
|
except Exception:
|
||||||
|
raise ClientSideError(_("Timezone %s is not valid")
|
||||||
|
% tc.timezone)
|
||||||
|
return tc
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sample(cls):
|
||||||
|
return cls(name='SampleConstraint',
|
||||||
|
description='nightly build every night at 23h for 3 hours',
|
||||||
|
start='0 23 * * *',
|
||||||
|
duration=10800,
|
||||||
|
timezone='Europe/Ljubljana')
|
||||||
|
|
||||||
|
|
||||||
class Alarm(_Base):
|
class Alarm(_Base):
|
||||||
"""Representation of an alarm.
|
"""Representation of an alarm.
|
||||||
|
|
||||||
@ -1560,6 +1627,9 @@ class Alarm(_Base):
|
|||||||
"""Describe when to trigger the alarm based on combining the state of
|
"""Describe when to trigger the alarm based on combining the state of
|
||||||
other alarms"""
|
other alarms"""
|
||||||
|
|
||||||
|
time_constraints = wtypes.wsattr([AlarmTimeConstraint], default=[])
|
||||||
|
"""Describe time constraints for the alarm"""
|
||||||
|
|
||||||
# These settings are ignored in the PUT or POST operations, but are
|
# These settings are ignored in the PUT or POST operations, but are
|
||||||
# filled in for GET
|
# filled in for GET
|
||||||
project_id = wtypes.text
|
project_id = wtypes.text
|
||||||
@ -1578,7 +1648,7 @@ 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"
|
||||||
|
|
||||||
def __init__(self, rule=None, **kwargs):
|
def __init__(self, rule=None, time_constraints=None, **kwargs):
|
||||||
super(Alarm, self).__init__(**kwargs)
|
super(Alarm, self).__init__(**kwargs)
|
||||||
|
|
||||||
if rule:
|
if rule:
|
||||||
@ -1586,6 +1656,9 @@ class Alarm(_Base):
|
|||||||
self.threshold_rule = AlarmThresholdRule(**rule)
|
self.threshold_rule = AlarmThresholdRule(**rule)
|
||||||
elif self.type == 'combination':
|
elif self.type == 'combination':
|
||||||
self.combination_rule = AlarmCombinationRule(**rule)
|
self.combination_rule = AlarmCombinationRule(**rule)
|
||||||
|
if time_constraints:
|
||||||
|
self.time_constraints = [AlarmTimeConstraint(**tc)
|
||||||
|
for tc in time_constraints]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(alarm):
|
def validate(alarm):
|
||||||
@ -1628,6 +1701,7 @@ class Alarm(_Base):
|
|||||||
type='combination',
|
type='combination',
|
||||||
threshold_rule=None,
|
threshold_rule=None,
|
||||||
combination_rule=AlarmCombinationRule.sample(),
|
combination_rule=AlarmCombinationRule.sample(),
|
||||||
|
time_constraints=[AlarmTimeConstraint.sample().as_dict()],
|
||||||
user_id="c96c887c216949acbdfbd8b494863567",
|
user_id="c96c887c216949acbdfbd8b494863567",
|
||||||
project_id="c96c887c216949acbdfbd8b494863567",
|
project_id="c96c887c216949acbdfbd8b494863567",
|
||||||
enabled=True,
|
enabled=True,
|
||||||
@ -1646,6 +1720,7 @@ class Alarm(_Base):
|
|||||||
if k.endswith('_rule'):
|
if k.endswith('_rule'):
|
||||||
del d[k]
|
del d[k]
|
||||||
d['rule'] = getattr(self, "%s_rule" % self.type).as_dict()
|
d['rule'] = getattr(self, "%s_rule" % self.type).as_dict()
|
||||||
|
d['time_constraints'] = [tc.as_dict() for tc in self.time_constraints]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
@ -785,6 +785,7 @@ class Connection(base.Connection):
|
|||||||
insufficient_data_actions=
|
insufficient_data_actions=
|
||||||
row.insufficient_data_actions,
|
row.insufficient_data_actions,
|
||||||
rule=row.rule,
|
rule=row.rule,
|
||||||
|
time_constraints=row.time_constraints,
|
||||||
repeat_actions=row.repeat_actions)
|
repeat_actions=row.repeat_actions)
|
||||||
|
|
||||||
def _retrieve_alarms(self, query):
|
def _retrieve_alarms(self, query):
|
||||||
|
@ -299,6 +299,7 @@ class Alarm(Model):
|
|||||||
:param project_id: the project_id of the creator
|
:param project_id: the project_id of the creator
|
||||||
:param evaluation_periods: the number of periods
|
:param evaluation_periods: the number of periods
|
||||||
:param period: the time period in seconds
|
:param period: the time period in seconds
|
||||||
|
:param time_constraints: the list of the alarm's time constraints, if any
|
||||||
:param timestamp: the timestamp when the alarm was last updated
|
:param timestamp: the timestamp when the alarm was last updated
|
||||||
:param state_timestamp: the timestamp of the last state change
|
:param state_timestamp: the timestamp of the last state change
|
||||||
:param ok_actions: the list of webhooks to call when entering the ok state
|
:param ok_actions: the list of webhooks to call when entering the ok state
|
||||||
@ -312,7 +313,7 @@ class Alarm(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,
|
ok_actions, alarm_actions, insufficient_data_actions,
|
||||||
repeat_actions, rule):
|
repeat_actions, rule, time_constraints):
|
||||||
Model.__init__(
|
Model.__init__(
|
||||||
self,
|
self,
|
||||||
alarm_id=alarm_id,
|
alarm_id=alarm_id,
|
||||||
@ -330,7 +331,8 @@ class Alarm(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)
|
||||||
|
|
||||||
|
|
||||||
class AlarmChange(Model):
|
class AlarmChange(Model):
|
||||||
|
@ -209,6 +209,7 @@ class Connection(base.Connection):
|
|||||||
stored_alarm = self.db.alarm.find({'alarm_id': alarm.alarm_id})[0]
|
stored_alarm = self.db.alarm.find({'alarm_id': alarm.alarm_id})[0]
|
||||||
del stored_alarm['_id']
|
del stored_alarm['_id']
|
||||||
self._ensure_encapsulated_rule_format(stored_alarm)
|
self._ensure_encapsulated_rule_format(stored_alarm)
|
||||||
|
self._ensure_time_constraints(stored_alarm)
|
||||||
return models.Alarm(**stored_alarm)
|
return models.Alarm(**stored_alarm)
|
||||||
|
|
||||||
create_alarm = update_alarm
|
create_alarm = update_alarm
|
||||||
@ -317,6 +318,7 @@ class Connection(base.Connection):
|
|||||||
a.update(alarm)
|
a.update(alarm)
|
||||||
del a['_id']
|
del a['_id']
|
||||||
self._ensure_encapsulated_rule_format(a)
|
self._ensure_encapsulated_rule_format(a)
|
||||||
|
self._ensure_time_constraints(a)
|
||||||
yield models.Alarm(**a)
|
yield models.Alarm(**a)
|
||||||
|
|
||||||
def _retrieve_alarm_changes(self, query_filter, orderby, limit):
|
def _retrieve_alarm_changes(self, query_filter, orderby, limit):
|
||||||
@ -397,6 +399,12 @@ class Connection(base.Connection):
|
|||||||
new_matching_metadata[elem['key']] = elem['value']
|
new_matching_metadata[elem['key']] = elem['value']
|
||||||
return new_matching_metadata
|
return new_matching_metadata
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensure_time_constraints(alarm):
|
||||||
|
"""Ensures the alarm has a time constraints field."""
|
||||||
|
if 'time_constraints' not in alarm:
|
||||||
|
alarm['time_constraints'] = []
|
||||||
|
|
||||||
|
|
||||||
class QueryTransformer(object):
|
class QueryTransformer(object):
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Author: Nejc Saje <nejc.saje@xlab.si>
|
||||||
|
#
|
||||||
|
# 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 Table
|
||||||
|
from sqlalchemy import Text
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(migrate_engine):
|
||||||
|
meta = MetaData(bind=migrate_engine)
|
||||||
|
alarm = Table('alarm', meta, autoload=True)
|
||||||
|
time_constraints = Column('time_constraints', Text())
|
||||||
|
alarm.create_column(time_constraints)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(migrate_engine):
|
||||||
|
meta = MetaData(bind=migrate_engine)
|
||||||
|
alarm = Table('alarm', meta, autoload=True)
|
||||||
|
time_constraints = Column('time_constraints', Text())
|
||||||
|
alarm.drop_column(time_constraints)
|
@ -309,6 +309,7 @@ class Alarm(Base):
|
|||||||
repeat_actions = Column(Boolean)
|
repeat_actions = Column(Boolean)
|
||||||
|
|
||||||
rule = Column(JSONEncodedDict)
|
rule = Column(JSONEncodedDict)
|
||||||
|
time_constraints = Column(JSONEncodedDict)
|
||||||
|
|
||||||
|
|
||||||
class AlarmChange(Base):
|
class AlarmChange(Base):
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
"""class for tests in ceilometer/alarm/evaluator/__init__.py
|
"""class for tests in ceilometer/alarm/evaluator/__init__.py
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
import mock
|
import mock
|
||||||
|
import pytz
|
||||||
|
|
||||||
from ceilometer.alarm import evaluator
|
from ceilometer.alarm import evaluator
|
||||||
from ceilometer.openstack.common import test
|
from ceilometer.openstack.common import test
|
||||||
|
from ceilometer.openstack.common import timeutils
|
||||||
|
|
||||||
|
|
||||||
class TestEvaluatorBaseClass(test.BaseTestCase):
|
class TestEvaluatorBaseClass(test.BaseTestCase):
|
||||||
@ -45,3 +48,92 @@ class TestEvaluatorBaseClass(test.BaseTestCase):
|
|||||||
ev._refresh(mock.MagicMock(), mock.MagicMock(),
|
ev._refresh(mock.MagicMock(), mock.MagicMock(),
|
||||||
mock.MagicMock(), mock.MagicMock())
|
mock.MagicMock(), mock.MagicMock())
|
||||||
self.assertTrue(self.called)
|
self.assertTrue(self.called)
|
||||||
|
|
||||||
|
def test_base_time_constraints(self):
|
||||||
|
alarm = mock.MagicMock()
|
||||||
|
alarm.time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 11 * * *', # daily at 11:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': ''},
|
||||||
|
{'name': 'test2',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 23 * * *', # daily at 23:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': ''},
|
||||||
|
]
|
||||||
|
cls = evaluator.Evaluator
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 1, 12, 0, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 2, 1, 0, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 2, 5, 0, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
def test_base_time_constraints_complex(self):
|
||||||
|
alarm = mock.MagicMock()
|
||||||
|
alarm.time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
# Every consecutive 2 minutes (from the 3rd to the 57th) past
|
||||||
|
# every consecutive 2 hours (between 3:00 and 12:59) on every day.
|
||||||
|
'start': '3-57/2 3-12/2 * * *',
|
||||||
|
'duration': 30,
|
||||||
|
'timezone': ''}
|
||||||
|
]
|
||||||
|
cls = evaluator.Evaluator
|
||||||
|
|
||||||
|
# test minutes inside
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 3, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 31, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 57, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
# test minutes outside
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 2, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 4, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 58, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
# test hours inside
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 3, 31, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 5, 31, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 11, 31, 0))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
# test hours outside
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 1, 31, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 4, 31, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
timeutils.set_time_override(datetime.datetime(2014, 1, 5, 12, 31, 0))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
def test_base_time_constraints_timezone(self):
|
||||||
|
alarm = mock.MagicMock()
|
||||||
|
alarm.time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 11 * * *', # daily at 11:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': 'Europe/Ljubljana'}
|
||||||
|
]
|
||||||
|
cls = evaluator.Evaluator
|
||||||
|
dt_eu = datetime.datetime(2014, 1, 1, 12, 0, 0,
|
||||||
|
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||||
|
dt_us = datetime.datetime(2014, 1, 1, 12, 0, 0,
|
||||||
|
tzinfo=pytz.timezone('US/Eastern'))
|
||||||
|
timeutils.set_time_override(dt_eu.astimezone(pytz.UTC))
|
||||||
|
self.assertTrue(cls.within_time_constraint(alarm))
|
||||||
|
|
||||||
|
timeutils.set_time_override(dt_us.astimezone(pytz.UTC))
|
||||||
|
self.assertFalse(cls.within_time_constraint(alarm))
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
"""Tests for ceilometer/alarm/threshold_evaluation.py
|
"""Tests for ceilometer/alarm/threshold_evaluation.py
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
import mock
|
import mock
|
||||||
|
import pytz
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from ceilometer.alarm.evaluator import combination
|
from ceilometer.alarm.evaluator import combination
|
||||||
|
from ceilometer.openstack.common import timeutils
|
||||||
from ceilometer.storage import models
|
from ceilometer.storage import models
|
||||||
from ceilometer.tests.alarm.evaluator import base
|
from ceilometer.tests.alarm.evaluator import base
|
||||||
from ceilometerclient import exc
|
from ceilometerclient import exc
|
||||||
@ -46,6 +49,7 @@ class TestEvaluate(base.TestEvaluatorBase):
|
|||||||
ok_actions=[],
|
ok_actions=[],
|
||||||
alarm_actions=[],
|
alarm_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(
|
rule=dict(
|
||||||
alarm_ids=[
|
alarm_ids=[
|
||||||
'9cfc3e51-2ff1-4b1d-ac01-c1bd4c6d0d1e',
|
'9cfc3e51-2ff1-4b1d-ac01-c1bd4c6d0d1e',
|
||||||
@ -66,6 +70,7 @@ class TestEvaluate(base.TestEvaluatorBase):
|
|||||||
ok_actions=[],
|
ok_actions=[],
|
||||||
alarm_actions=[],
|
alarm_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(
|
rule=dict(
|
||||||
alarm_ids=[
|
alarm_ids=[
|
||||||
'b82734f4-9d06-48f3-8a86-fa59a0c99dc8',
|
'b82734f4-9d06-48f3-8a86-fa59a0c99dc8',
|
||||||
@ -304,3 +309,69 @@ class TestEvaluate(base.TestEvaluatorBase):
|
|||||||
in zip(self.alarms, reasons, reason_datas)]
|
in zip(self.alarms, reasons, reason_datas)]
|
||||||
|
|
||||||
self.assertEqual(self.notifier.notify.call_args_list, expected)
|
self.assertEqual(self.notifier.notify.call_args_list, expected)
|
||||||
|
|
||||||
|
def test_state_change_inside_time_constraint(self):
|
||||||
|
self._set_all_alarms('insufficient data')
|
||||||
|
self.alarms[0].time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 11 * * *', # daily at 11:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': 'Europe/Ljubljana'}
|
||||||
|
]
|
||||||
|
self.alarms[1].time_constraints = self.alarms[0].time_constraints
|
||||||
|
dt = datetime.datetime(2014, 1, 1, 12, 0, 0,
|
||||||
|
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||||
|
with mock.patch('ceilometerclient.client.get_client',
|
||||||
|
return_value=self.api_client):
|
||||||
|
timeutils.set_time_override(dt.astimezone(pytz.UTC))
|
||||||
|
self.api_client.alarms.get.side_effect = [
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
]
|
||||||
|
self._evaluate_all_alarms()
|
||||||
|
expected = [mock.call(alarm.alarm_id, state='ok')
|
||||||
|
for alarm in self.alarms]
|
||||||
|
update_calls = self.api_client.alarms.set_state.call_args_list
|
||||||
|
self.assertEqual(expected, update_calls,
|
||||||
|
"Alarm should change state if the current "
|
||||||
|
"time is inside its time constraint.")
|
||||||
|
reasons, reason_datas = self._combination_transition_reason(
|
||||||
|
'ok',
|
||||||
|
self.alarms[0].rule['alarm_ids'],
|
||||||
|
self.alarms[1].rule['alarm_ids'])
|
||||||
|
expected = [mock.call(alarm, 'insufficient data',
|
||||||
|
reason, reason_data)
|
||||||
|
for alarm, reason, reason_data
|
||||||
|
in zip(self.alarms, reasons, reason_datas)]
|
||||||
|
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||||
|
|
||||||
|
def test_no_state_change_outside_time_constraint(self):
|
||||||
|
self._set_all_alarms('insufficient data')
|
||||||
|
self.alarms[0].time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 11 * * *', # daily at 11:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': 'Europe/Ljubljana'}
|
||||||
|
]
|
||||||
|
self.alarms[1].time_constraints = self.alarms[0].time_constraints
|
||||||
|
dt = datetime.datetime(2014, 1, 1, 15, 0, 0,
|
||||||
|
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||||
|
with mock.patch('ceilometerclient.client.get_client',
|
||||||
|
return_value=self.api_client):
|
||||||
|
timeutils.set_time_override(dt.astimezone(pytz.UTC))
|
||||||
|
self.api_client.alarms.get.side_effect = [
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
self._get_alarm('ok'),
|
||||||
|
]
|
||||||
|
self._evaluate_all_alarms()
|
||||||
|
update_calls = self.api_client.alarms.set_state.call_args_list
|
||||||
|
self.assertEqual([], update_calls,
|
||||||
|
"Alarm should not change state if the current "
|
||||||
|
" time is outside its time constraint.")
|
||||||
|
self.assertEqual([], self.notifier.notify.call_args_list)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import mock
|
import mock
|
||||||
|
import pytz
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from six import moves
|
from six import moves
|
||||||
@ -51,6 +52,7 @@ class TestEvaluate(base.TestEvaluatorBase):
|
|||||||
ok_actions=[],
|
ok_actions=[],
|
||||||
alarm_actions=[],
|
alarm_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(
|
rule=dict(
|
||||||
comparison_operator='gt',
|
comparison_operator='gt',
|
||||||
threshold=80.0,
|
threshold=80.0,
|
||||||
@ -79,6 +81,7 @@ class TestEvaluate(base.TestEvaluatorBase):
|
|||||||
alarm_actions=[],
|
alarm_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
alarm_id=str(uuid.uuid4()),
|
alarm_id=str(uuid.uuid4()),
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(
|
rule=dict(
|
||||||
comparison_operator='le',
|
comparison_operator='le',
|
||||||
threshold=10.0,
|
threshold=10.0,
|
||||||
@ -438,3 +441,64 @@ class TestEvaluate(base.TestEvaluatorBase):
|
|||||||
|
|
||||||
def test_simple_alarm_no_clear_without_outlier_exclusion(self):
|
def test_simple_alarm_no_clear_without_outlier_exclusion(self):
|
||||||
self. _do_test_simple_alarm_clear_outlier_exclusion(False)
|
self. _do_test_simple_alarm_clear_outlier_exclusion(False)
|
||||||
|
|
||||||
|
def test_state_change_inside_time_constraint(self):
|
||||||
|
self._set_all_alarms('ok')
|
||||||
|
self.alarms[0].time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 11 * * *', # daily at 11:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': 'Europe/Ljubljana'}
|
||||||
|
]
|
||||||
|
self.alarms[1].time_constraints = self.alarms[0].time_constraints
|
||||||
|
dt = datetime.datetime(2014, 1, 1, 12, 0, 0,
|
||||||
|
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||||
|
with mock.patch('ceilometerclient.client.get_client',
|
||||||
|
return_value=self.api_client):
|
||||||
|
timeutils.set_time_override(dt.astimezone(pytz.UTC))
|
||||||
|
# the following part based on test_simple_insufficient
|
||||||
|
self.api_client.statistics.list.return_value = []
|
||||||
|
self._evaluate_all_alarms()
|
||||||
|
self._assert_all_alarms('insufficient data')
|
||||||
|
expected = [mock.call(alarm.alarm_id,
|
||||||
|
state='insufficient data')
|
||||||
|
for alarm in self.alarms]
|
||||||
|
update_calls = self.api_client.alarms.set_state.call_args_list
|
||||||
|
self.assertEqual(expected, update_calls,
|
||||||
|
"Alarm should change state if the current "
|
||||||
|
"time is inside its time constraint.")
|
||||||
|
expected = [mock.call(
|
||||||
|
alarm,
|
||||||
|
'ok',
|
||||||
|
('%d datapoints are unknown'
|
||||||
|
% alarm.rule['evaluation_periods']),
|
||||||
|
self._reason_data('unknown',
|
||||||
|
alarm.rule['evaluation_periods'],
|
||||||
|
None))
|
||||||
|
for alarm in self.alarms]
|
||||||
|
self.assertEqual(expected, self.notifier.notify.call_args_list)
|
||||||
|
|
||||||
|
def test_no_state_change_outside_time_constraint(self):
|
||||||
|
self._set_all_alarms('ok')
|
||||||
|
self.alarms[0].time_constraints = [
|
||||||
|
{'name': 'test',
|
||||||
|
'description': 'test',
|
||||||
|
'start': '0 11 * * *', # daily at 11:00
|
||||||
|
'duration': 10800, # 3 hours
|
||||||
|
'timezone': 'Europe/Ljubljana'}
|
||||||
|
]
|
||||||
|
self.alarms[1].time_constraints = self.alarms[0].time_constraints
|
||||||
|
dt = datetime.datetime(2014, 1, 1, 15, 0, 0,
|
||||||
|
tzinfo=pytz.timezone('Europe/Ljubljana'))
|
||||||
|
with mock.patch('ceilometerclient.client.get_client',
|
||||||
|
return_value=self.api_client):
|
||||||
|
timeutils.set_time_override(dt.astimezone(pytz.UTC))
|
||||||
|
self.api_client.statistics.list.return_value = []
|
||||||
|
self._evaluate_all_alarms()
|
||||||
|
self._assert_all_alarms('ok')
|
||||||
|
update_calls = self.api_client.alarms.set_state.call_args_list
|
||||||
|
self.assertEqual([], update_calls,
|
||||||
|
"Alarm should not change state if the current "
|
||||||
|
" time is outside its time constraint.")
|
||||||
|
self.assertEqual([], self.notifier.notify.call_args_list)
|
||||||
|
@ -100,6 +100,7 @@ class TestCoordinate(test.BaseTestCase):
|
|||||||
alarm_actions=[],
|
alarm_actions=[],
|
||||||
insufficient_data_actions=[],
|
insufficient_data_actions=[],
|
||||||
alarm_id=uuid,
|
alarm_id=uuid,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(
|
rule=dict(
|
||||||
statistic='avg',
|
statistic='avg',
|
||||||
comparison_operator='gt',
|
comparison_operator='gt',
|
||||||
|
@ -70,6 +70,9 @@ class TestAlarms(FunctionalTest,
|
|||||||
repeat_actions=True,
|
repeat_actions=True,
|
||||||
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=[dict(name='testcons',
|
||||||
|
start='0 11 * * *',
|
||||||
|
duration=300)],
|
||||||
rule=dict(comparison_operator='gt',
|
rule=dict(comparison_operator='gt',
|
||||||
threshold=2.0,
|
threshold=2.0,
|
||||||
statistic='avg',
|
statistic='avg',
|
||||||
@ -95,6 +98,7 @@ class TestAlarms(FunctionalTest,
|
|||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
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=[],
|
||||||
rule=dict(comparison_operator='gt',
|
rule=dict(comparison_operator='gt',
|
||||||
threshold=4.0,
|
threshold=4.0,
|
||||||
statistic='avg',
|
statistic='avg',
|
||||||
@ -120,6 +124,7 @@ class TestAlarms(FunctionalTest,
|
|||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
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=[],
|
||||||
rule=dict(comparison_operator='gt',
|
rule=dict(comparison_operator='gt',
|
||||||
threshold=3.0,
|
threshold=3.0,
|
||||||
statistic='avg',
|
statistic='avg',
|
||||||
@ -145,6 +150,7 @@ class TestAlarms(FunctionalTest,
|
|||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
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=[],
|
||||||
rule=dict(alarm_ids=['a', 'b'],
|
rule=dict(alarm_ids=['a', 'b'],
|
||||||
operator='or')
|
operator='or')
|
||||||
)]:
|
)]:
|
||||||
@ -215,6 +221,8 @@ class TestAlarms(FunctionalTest,
|
|||||||
'meter.test')
|
'meter.test')
|
||||||
self.assertEqual(one['alarm_id'], alarms[0]['alarm_id'])
|
self.assertEqual(one['alarm_id'], alarms[0]['alarm_id'])
|
||||||
self.assertEqual(one['repeat_actions'], alarms[0]['repeat_actions'])
|
self.assertEqual(one['repeat_actions'], alarms[0]['repeat_actions'])
|
||||||
|
self.assertEqual(one['time_constraints'],
|
||||||
|
alarms[0]['time_constraints'])
|
||||||
|
|
||||||
def test_get_alarm_disabled(self):
|
def test_get_alarm_disabled(self):
|
||||||
alarm = models.Alarm(name='disabled',
|
alarm = models.Alarm(name='disabled',
|
||||||
@ -231,6 +239,7 @@ class TestAlarms(FunctionalTest,
|
|||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
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=[],
|
||||||
rule=dict(alarm_ids=['a', 'b'], operator='or'))
|
rule=dict(alarm_ids=['a', 'b'], operator='or'))
|
||||||
self.conn.update_alarm(alarm)
|
self.conn.update_alarm(alarm)
|
||||||
|
|
||||||
@ -308,6 +317,70 @@ class TestAlarms(FunctionalTest,
|
|||||||
alarms = list(self.conn.get_alarms())
|
alarms = list(self.conn.get_alarms())
|
||||||
self.assertEqual(4, len(alarms))
|
self.assertEqual(4, len(alarms))
|
||||||
|
|
||||||
|
def test_post_invalid_alarm_time_constraint_start(self):
|
||||||
|
json = {
|
||||||
|
'name': 'added_alarm_invalid_constraint_duration',
|
||||||
|
'type': 'threshold',
|
||||||
|
'time_constraints': [
|
||||||
|
{
|
||||||
|
'name': 'testcons',
|
||||||
|
'start': '11:00am',
|
||||||
|
'duration': 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'threshold_rule': {
|
||||||
|
'meter_name': 'ameter',
|
||||||
|
'threshold': 300.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
|
headers=self.auth_headers)
|
||||||
|
alarms = list(self.conn.get_alarms())
|
||||||
|
self.assertEqual(4, len(alarms))
|
||||||
|
|
||||||
|
def test_post_invalid_alarm_time_constraint_duration(self):
|
||||||
|
json = {
|
||||||
|
'name': 'added_alarm_invalid_constraint_duration',
|
||||||
|
'type': 'threshold',
|
||||||
|
'time_constraints': [
|
||||||
|
{
|
||||||
|
'name': 'testcons',
|
||||||
|
'start': '* 11 * * *',
|
||||||
|
'duration': -1,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'threshold_rule': {
|
||||||
|
'meter_name': 'ameter',
|
||||||
|
'threshold': 300.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
|
headers=self.auth_headers)
|
||||||
|
alarms = list(self.conn.get_alarms())
|
||||||
|
self.assertEqual(4, len(alarms))
|
||||||
|
|
||||||
|
def test_post_invalid_alarm_time_constraint_timezone(self):
|
||||||
|
json = {
|
||||||
|
'name': 'added_alarm_invalid_constraint_timezone',
|
||||||
|
'type': 'threshold',
|
||||||
|
'time_constraints': [
|
||||||
|
{
|
||||||
|
'name': 'testcons',
|
||||||
|
'start': '* 11 * * *',
|
||||||
|
'duration': 10,
|
||||||
|
'timezone': 'aaaa'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'threshold_rule': {
|
||||||
|
'meter_name': 'ameter',
|
||||||
|
'threshold': 300.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.post_json('/alarms', params=json, expect_errors=True, status=400,
|
||||||
|
headers=self.auth_headers)
|
||||||
|
alarms = list(self.conn.get_alarms())
|
||||||
|
self.assertEqual(4, len(alarms))
|
||||||
|
|
||||||
def test_post_invalid_alarm_period(self):
|
def test_post_invalid_alarm_period(self):
|
||||||
json = {
|
json = {
|
||||||
'name': 'added_alarm_invalid_period',
|
'name': 'added_alarm_invalid_period',
|
||||||
@ -1120,8 +1193,9 @@ class TestAlarms(FunctionalTest,
|
|||||||
self.assertIsNotNone(actual['event_id'])
|
self.assertIsNotNone(actual['event_id'])
|
||||||
|
|
||||||
def _assert_in_json(self, expected, actual):
|
def _assert_in_json(self, expected, actual):
|
||||||
|
actual = jsonutils.dumps(jsonutils.loads(actual), sort_keys=True)
|
||||||
for k, v in expected.iteritems():
|
for k, v in expected.iteritems():
|
||||||
fragment = jsonutils.dumps({k: v})[1:-1]
|
fragment = jsonutils.dumps({k: v}, sort_keys=True)[1:-1]
|
||||||
self.assertTrue(fragment in actual,
|
self.assertTrue(fragment in actual,
|
||||||
'%s not in %s' % (fragment, actual))
|
'%s not in %s' % (fragment, actual))
|
||||||
|
|
||||||
|
@ -301,6 +301,7 @@ class TestQueryAlarmsController(tests_api.FunctionalTest,
|
|||||||
repeat_actions=True,
|
repeat_actions=True,
|
||||||
user_id="user-id%d" % id,
|
user_id="user-id%d" % id,
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(comparison_operator='gt',
|
rule=dict(comparison_operator='gt',
|
||||||
threshold=2.0,
|
threshold=2.0,
|
||||||
statistic='avg',
|
statistic='avg',
|
||||||
|
@ -256,6 +256,10 @@ class CompatibilityTest(test_storage_scenarios.DBTestBase,
|
|||||||
self.assertEqual(old.rule['comparison_operator'], 'lt')
|
self.assertEqual(old.rule['comparison_operator'], 'lt')
|
||||||
self.assertEqual(old.rule['threshold'], 36)
|
self.assertEqual(old.rule['threshold'], 36)
|
||||||
|
|
||||||
|
def test_alarm_without_time_constraints(self):
|
||||||
|
old = list(self.conn.get_alarms(name='other-old-alaert'))[0]
|
||||||
|
self.assertEqual([], old.time_constraints)
|
||||||
|
|
||||||
def test_counter_unit(self):
|
def test_counter_unit(self):
|
||||||
meters = list(self.conn.get_meters())
|
meters = list(self.conn.get_meters())
|
||||||
self.assertEqual(len(meters), 1)
|
self.assertEqual(len(meters), 1)
|
||||||
|
@ -71,7 +71,8 @@ class ModelTest(test.BaseTestCase):
|
|||||||
alarm_fields = ["alarm_id", "type", "enabled", "name", "description",
|
alarm_fields = ["alarm_id", "type", "enabled", "name", "description",
|
||||||
"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"]
|
||||||
|
|
||||||
self.assertEqual(set(alarm_fields),
|
self.assertEqual(set(alarm_fields),
|
||||||
set(models.Alarm.get_field_names()))
|
set(models.Alarm.get_field_names()))
|
||||||
|
@ -696,6 +696,7 @@ class RawSampleTest(DBTestBase,
|
|||||||
alarm_actions=['http://nowhere/alarms'],
|
alarm_actions=['http://nowhere/alarms'],
|
||||||
insufficient_data_actions=[],
|
insufficient_data_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(comparison_operator='eq',
|
rule=dict(comparison_operator='eq',
|
||||||
threshold=36,
|
threshold=36,
|
||||||
statistic='count',
|
statistic='count',
|
||||||
@ -2312,6 +2313,9 @@ class AlarmTestBase(DBTestBase):
|
|||||||
alarm_actions=['http://nowhere/alarms'],
|
alarm_actions=['http://nowhere/alarms'],
|
||||||
insufficient_data_actions=[],
|
insufficient_data_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[dict(name='testcons',
|
||||||
|
start='0 11 * * *',
|
||||||
|
duration=300)],
|
||||||
rule=dict(comparison_operator='eq',
|
rule=dict(comparison_operator='eq',
|
||||||
threshold=36,
|
threshold=36,
|
||||||
statistic='count',
|
statistic='count',
|
||||||
@ -2337,6 +2341,7 @@ class AlarmTestBase(DBTestBase):
|
|||||||
alarm_actions=['http://nowhere/alarms'],
|
alarm_actions=['http://nowhere/alarms'],
|
||||||
insufficient_data_actions=[],
|
insufficient_data_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(comparison_operator='gt',
|
rule=dict(comparison_operator='gt',
|
||||||
threshold=75,
|
threshold=75,
|
||||||
statistic='avg',
|
statistic='avg',
|
||||||
@ -2362,6 +2367,7 @@ class AlarmTestBase(DBTestBase):
|
|||||||
alarm_actions=['http://nowhere/alarms'],
|
alarm_actions=['http://nowhere/alarms'],
|
||||||
insufficient_data_actions=[],
|
insufficient_data_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(comparison_operator='lt',
|
rule=dict(comparison_operator='lt',
|
||||||
threshold=10,
|
threshold=10,
|
||||||
statistic='min',
|
statistic='min',
|
||||||
@ -2446,6 +2452,7 @@ class AlarmTest(AlarmTestBase,
|
|||||||
alarm_actions=[],
|
alarm_actions=[],
|
||||||
insufficient_data_actions=[],
|
insufficient_data_actions=[],
|
||||||
repeat_actions=False,
|
repeat_actions=False,
|
||||||
|
time_constraints=[],
|
||||||
rule=dict(comparison_operator='lt',
|
rule=dict(comparison_operator='lt',
|
||||||
threshold=34,
|
threshold=34,
|
||||||
statistic='max',
|
statistic='max',
|
||||||
|
@ -57,6 +57,9 @@ Alarms
|
|||||||
.. autotype:: ceilometer.api.controllers.v2.AlarmCombinationRule
|
.. autotype:: ceilometer.api.controllers.v2.AlarmCombinationRule
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autotype:: ceilometer.api.controllers.v2.AlarmTimeConstraint
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autotype:: ceilometer.api.controllers.v2.AlarmChange
|
.. autotype:: ceilometer.api.controllers.v2.AlarmChange
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
alembic>=0.4.1
|
alembic>=0.4.1
|
||||||
anyjson>=0.3.3
|
anyjson>=0.3.3
|
||||||
argparse
|
argparse
|
||||||
|
croniter>=0.3.4
|
||||||
eventlet>=0.13.0
|
eventlet>=0.13.0
|
||||||
Flask>=0.10,<1.0
|
Flask>=0.10,<1.0
|
||||||
happybase>=0.4,<=0.6
|
happybase>=0.4,<=0.6
|
||||||
@ -20,6 +21,7 @@ python-glanceclient>=0.9.0
|
|||||||
python-keystoneclient>=0.6.0
|
python-keystoneclient>=0.6.0
|
||||||
python-novaclient>=2.15.0
|
python-novaclient>=2.15.0
|
||||||
python-swiftclient>=1.6
|
python-swiftclient>=1.6
|
||||||
|
pytz>=2010h
|
||||||
PyYAML>=3.1.0
|
PyYAML>=3.1.0
|
||||||
requests>=1.1
|
requests>=1.1
|
||||||
six>=1.5.2
|
six>=1.5.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user