diff --git a/ceilometer/api/controllers/root.py b/ceilometer/api/controllers/root.py index cf13160f..4f22862a 100644 --- a/ceilometer/api/controllers/root.py +++ b/ceilometer/api/controllers/root.py @@ -15,7 +15,7 @@ import pecan -from ceilometer.api.controllers import v2 +from ceilometer.api.controllers.v2 import root as v2 MEDIA_TYPE_JSON = 'application/vnd.openstack.telemetry-%s+json' MEDIA_TYPE_XML = 'application/vnd.openstack.telemetry-%s+xml' diff --git a/ceilometer/api/controllers/v2/__init__.py b/ceilometer/api/controllers/v2/__init__.py index 6379f9ae..e69de29b 100644 --- a/ceilometer/api/controllers/v2/__init__.py +++ b/ceilometer/api/controllers/v2/__init__.py @@ -1,40 +0,0 @@ -# -# Copyright 2012 New Dream Network, LLC (DreamHost) -# Copyright 2013 IBM Corp. -# Copyright 2013 eNovance -# Copyright Ericsson AB 2013. All rights reserved -# Copyright 2014 Hewlett-Packard Company -# Copyright 2015 Huawei Technologies Co., Ltd. -# -# 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 ceilometer.api.controllers.v2 import alarms -from ceilometer.api.controllers.v2 import capabilities -from ceilometer.api.controllers.v2 import events -from ceilometer.api.controllers.v2 import meters -from ceilometer.api.controllers.v2 import query -from ceilometer.api.controllers.v2 import resources -from ceilometer.api.controllers.v2 import samples - - -class V2Controller(object): - """Version 2 API controller root.""" - - resources = resources.ResourcesController() - meters = meters.MetersController() - samples = samples.SamplesController() - alarms = alarms.AlarmsController() - event_types = events.EventTypesController() - events = events.EventsController() - query = query.QueryController() - capabilities = capabilities.CapabilitiesController() diff --git a/ceilometer/api/controllers/v2/alarms.py b/ceilometer/api/controllers/v2/alarms.py index 1ae7328f..1f0422ac 100644 --- a/ceilometer/api/controllers/v2/alarms.py +++ b/ceilometer/api/controllers/v2/alarms.py @@ -31,6 +31,7 @@ import pecan from pecan import rest import pytz import six +from stevedore import extension import wsme from wsme import types as wtypes import wsmeext.pecan as wsme_pecan @@ -133,7 +134,12 @@ class CronType(wtypes.UserType): return value -class AlarmThresholdRule(base.Base): +class AlarmThresholdRule(base.AlarmRule): + """Alarm Threshold Rule + + Describe when to trigger the alarm based on computed statistics + """ + meter_name = wsme.wsattr(wtypes.text, mandatory=True) "The name of the meter" @@ -186,6 +192,16 @@ class AlarmThresholdRule(base.Base): allow_timestamps=False) return threshold_rule + @staticmethod + def validate_alarm(alarm): + # ensure an implicit constraint on project_id is added to + # the query if not already present + alarm.threshold_rule.query = v2_utils.sanitize_query( + alarm.threshold_rule.query, + storage.SampleFilter.__init__, + on_behalf_of=alarm.project_id + ) + @property def default_description(self): return (_('Alarm when %(meter_name)s is %(comparison_operator)s a ' @@ -218,7 +234,13 @@ class AlarmThresholdRule(base.Base): 'type': 'string'}]) -class AlarmCombinationRule(base.Base): +class AlarmCombinationRule(base.AlarmRule): + """Alarm Combinarion Rule + + Describe when to trigger the alarm based on combining the state of + other alarms. + """ + operator = base.AdvEnum('operator', str, 'or', 'and', default='and') "How to combine the sub-alarms" @@ -242,6 +264,25 @@ class AlarmCombinationRule(base.Base): 'alarm ids.')) return rule + @staticmethod + def validate_alarm(alarm): + project = v2_utils.get_auth_project( + alarm.project_id if alarm.project_id != wtypes.Unset else None) + for id in alarm.combination_rule.alarm_ids: + alarms = list(pecan.request.alarm_storage_conn.get_alarms( + alarm_id=id, project=project)) + if not alarms: + raise AlarmNotFound(id, project) + + @staticmethod + def update_hook(alarm): + # should check if there is any circle in the dependency, but for + # efficiency reason, here only check alarm cannot depend on itself + if alarm.alarm_id in alarm.combination_rule.alarm_ids: + raise base.ClientSideError( + _('Cannot specify alarm %s itself in combination rule') % + alarm.alarm_id) + @classmethod def sample(cls): return cls(operator='or', @@ -302,6 +343,10 @@ class AlarmTimeConstraint(base.Base): timezone='Europe/Ljubljana') +ALARMS_RULES = extension.ExtensionManager("ceilometer.alarm.rule") +LOG.debug("alarm rules plugin loaded: %s" % ",".join(ALARMS_RULES.names())) + + class Alarm(base.Base): """Representation of an alarm. @@ -347,17 +392,10 @@ class Alarm(base.Base): repeat_actions = wsme.wsattr(bool, default=False) "The actions should be re-triggered on each evaluation cycle" - type = base.AdvEnum('type', str, 'threshold', 'combination', + type = base.AdvEnum('type', str, *ALARMS_RULES.names(), mandatory=True) "Explicit type specifier to select which rule to follow below." - threshold_rule = AlarmThresholdRule - "Describe when to trigger the alarm based on computed statistics" - - combination_rule = AlarmCombinationRule - """Describe when to trigger the alarm based on combining the state of - other alarms""" - time_constraints = wtypes.wsattr([AlarmTimeConstraint], default=[]) """Describe time constraints for the alarm""" @@ -387,10 +425,9 @@ class Alarm(base.Base): super(Alarm, self).__init__(**kwargs) if rule: - if self.type == 'threshold': - self.threshold_rule = AlarmThresholdRule(**rule) - elif self.type == 'combination': - self.combination_rule = AlarmCombinationRule(**rule) + setattr(self, '%s_rule' % self.type, + ALARMS_RULES[self.type].plugin(**rule)) + if time_constraints: self.time_constraints = [AlarmTimeConstraint(**tc) for tc in time_constraints] @@ -400,22 +437,8 @@ class Alarm(base.Base): Alarm.check_rule(alarm) Alarm.check_alarm_actions(alarm) - if alarm.threshold_rule: - # ensure an implicit constraint on project_id is added to - # the query if not already present - alarm.threshold_rule.query = v2_utils.sanitize_query( - alarm.threshold_rule.query, - storage.SampleFilter.__init__, - on_behalf_of=alarm.project_id - ) - elif alarm.combination_rule: - project = v2_utils.get_auth_project( - alarm.project_id if alarm.project_id != wtypes.Unset else None) - for id in alarm.combination_rule.alarm_ids: - alarms = list(pecan.request.alarm_storage_conn.get_alarms( - alarm_id=id, project=project)) - if not alarms: - raise AlarmNotFound(id, project) + + ALARMS_RULES[alarm.type].plugin.validate_alarm(alarm) tc_names = [tc.name for tc in alarm.time_constraints] if len(tc_names) > len(set(tc_names)): @@ -432,10 +455,17 @@ class Alarm(base.Base): error = _("%(rule)s must be set for %(type)s" " type alarm") % {"rule": rule, "type": alarm.type} raise base.ClientSideError(error) - if alarm.threshold_rule and alarm.combination_rule: - error = _("threshold_rule and combination_rule " - "cannot be set at the same time") - raise base.ClientSideError(error) + + rule_set = None + for ext in ALARMS_RULES: + name = "%s_rule" % ext.name + if getattr(alarm, name): + if rule_set is None: + rule_set = name + else: + error = _("%(rule1)s and %(rule2)s cannot be set at the " + "same time") % {'rule1': rule_set, 'rule2': name} + raise base.ClientSideError(error) @staticmethod def check_alarm_actions(alarm): @@ -462,8 +492,6 @@ class Alarm(base.Base): name="SwiftObjectAlarm", description="An alarm", type='combination', - threshold_rule=None, - combination_rule=AlarmCombinationRule.sample(), time_constraints=[AlarmTimeConstraint.sample().as_dict()], user_id="c96c887c216949acbdfbd8b494863567", project_id="c96c887c216949acbdfbd8b494863567", @@ -487,6 +515,9 @@ class Alarm(base.Base): d['time_constraints'] = [tc.as_dict() for tc in self.time_constraints] return d +Alarm.add_attributes(**{"%s_rule" % ext.name: ext.plugin + for ext in ALARMS_RULES}) + class AlarmChange(base.Base): """Representation of an event in an alarm's history.""" @@ -639,13 +670,7 @@ class AlarmController(rest.RestController): _("Alarm with name=%s exists") % data.name, status_code=409) - # should check if there is any circle in the dependency, but for - # efficiency reason, here only check alarm cannot depend on itself - if data.type == 'combination': - if self._id in data.combination_rule.alarm_ids: - raise base.ClientSideError( - _('Cannot specify alarm %s itself in ' - 'combination rule') % self._id) + ALARMS_RULES[data.type].plugin.update_hook(data) old_alarm = Alarm.from_db_model(alarm_in).as_dict(alarm_models.Alarm) updated_alarm = data.as_dict(alarm_models.Alarm) @@ -805,6 +830,8 @@ class AlarmsController(rest.RestController): data.timestamp = now data.state_timestamp = now + ALARMS_RULES[data.type].plugin.create_hook(data) + change = data.as_dict(alarm_models.Alarm) # make sure alarms are unique by name per project. diff --git a/ceilometer/api/controllers/v2/base.py b/ceilometer/api/controllers/v2/base.py index ddebb382..07c3e96d 100644 --- a/ceilometer/api/controllers/v2/base.py +++ b/ceilometer/api/controllers/v2/base.py @@ -83,7 +83,7 @@ class AdvEnum(wtypes.wsproperty): value, e) -class Base(wtypes.Base): +class Base(wtypes.DynamicBase): @classmethod def from_db_model(cls, m): @@ -228,3 +228,18 @@ class Query(Base): {'value': self.value, 'type': type}) raise ClientSideError(msg) return converted_value + + +class AlarmRule(Base): + """Base class Alarm Rule extension and wsme.types.""" + @staticmethod + def validate_alarm(alarm): + pass + + @staticmethod + def create_hook(alarm): + pass + + @staticmethod + def update_hook(alarm): + pass diff --git a/ceilometer/api/controllers/v2/root.py b/ceilometer/api/controllers/v2/root.py new file mode 100644 index 00000000..6379f9ae --- /dev/null +++ b/ceilometer/api/controllers/v2/root.py @@ -0,0 +1,40 @@ +# +# Copyright 2012 New Dream Network, LLC (DreamHost) +# Copyright 2013 IBM Corp. +# Copyright 2013 eNovance +# Copyright Ericsson AB 2013. All rights reserved +# Copyright 2014 Hewlett-Packard Company +# Copyright 2015 Huawei Technologies Co., Ltd. +# +# 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 ceilometer.api.controllers.v2 import alarms +from ceilometer.api.controllers.v2 import capabilities +from ceilometer.api.controllers.v2 import events +from ceilometer.api.controllers.v2 import meters +from ceilometer.api.controllers.v2 import query +from ceilometer.api.controllers.v2 import resources +from ceilometer.api.controllers.v2 import samples + + +class V2Controller(object): + """Version 2 API controller root.""" + + resources = resources.ResourcesController() + meters = meters.MetersController() + samples = samples.SamplesController() + alarms = alarms.AlarmsController() + event_types = events.EventTypesController() + events = events.EventsController() + query = query.QueryController() + capabilities = capabilities.CapabilitiesController() diff --git a/setup.cfg b/setup.cfg index 384d7259..a64a91b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -271,6 +271,10 @@ ceilometer.event.publisher = direct = ceilometer.publisher.direct:DirectPublisher notifier = ceilometer.publisher.messaging:EventNotifierPublisher +ceilometer.alarm.rule = + threshold = ceilometer.api.controllers.v2.alarms:AlarmThresholdRule + combination = ceilometer.api.controllers.v2.alarms:AlarmCombinationRule + ceilometer.alarm.evaluator = threshold = ceilometer.alarm.evaluator.threshold:ThresholdEvaluator combination = ceilometer.alarm.evaluator.combination:CombinationEvaluator