Use stevedore to load alarm rules api
Alarm rule evaluators are loadable with stevedore, but not the API side, since wsme 0.6 it's now possible to do it. This change adds that to allow to create a gnocchi alarm rule api and evaluator in the gnocchi code tree. This also move out the V2Controller API from __init__ to avoid recursive import with stevedore. Change-Id: I468f9f82b41eb7f4896dd93a8c62ce63b49535bb
This commit is contained in:
parent
b4093e9347
commit
2e75325060
@ -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'
|
||||
|
@ -1,40 +0,0 @@
|
||||
#
|
||||
# Copyright 2012 New Dream Network, LLC (DreamHost)
|
||||
# Copyright 2013 IBM Corp.
|
||||
# Copyright 2013 eNovance <licensing@enovance.com>
|
||||
# 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()
|
@ -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,9 +455,16 @@ 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")
|
||||
|
||||
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
|
||||
@ -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.
|
||||
|
@ -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
|
||||
|
40
ceilometer/api/controllers/v2/root.py
Normal file
40
ceilometer/api/controllers/v2/root.py
Normal file
@ -0,0 +1,40 @@
|
||||
#
|
||||
# Copyright 2012 New Dream Network, LLC (DreamHost)
|
||||
# Copyright 2013 IBM Corp.
|
||||
# Copyright 2013 eNovance <licensing@enovance.com>
|
||||
# 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()
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user