Remove ceilometer-api alarm type
This change removes the deprecated 'threshold' alarm type from Aodh. This also removes the useless ceilometerclient dependency. Depends-On: Id89130fd8a782dea863c85b37a919fbf0f0897dd Change-Id: I7ccf930f2ad0316dbda0675a2ec0344e61986022
This commit is contained in:
parent
fb6d414ab7
commit
b53d862bb5
@ -41,8 +41,7 @@ class CompositeRule(wtypes.UserType):
|
||||
threshold_plugins = None
|
||||
|
||||
def __init__(self):
|
||||
threshold_rules = ('threshold',
|
||||
'gnocchi_resources_threshold',
|
||||
threshold_rules = ('gnocchi_resources_threshold',
|
||||
'gnocchi_aggregation_by_metrics_threshold',
|
||||
'gnocchi_aggregation_by_resources_threshold')
|
||||
CompositeRule.threshold_plugins = named.NamedExtensionManager(
|
||||
|
@ -1,161 +0,0 @@
|
||||
#
|
||||
# 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 ceilometerclient import client as ceiloclient
|
||||
from ceilometerclient import exc as ceiloexc
|
||||
import pecan
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from aodh.api.controllers.v2 import base
|
||||
from aodh.api.controllers.v2 import utils as v2_utils
|
||||
from aodh.i18n import _
|
||||
from aodh import keystone_client
|
||||
from aodh import storage
|
||||
|
||||
|
||||
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"
|
||||
|
||||
# FIXME(sileht): default doesn't work
|
||||
# workaround: default is set in validate method
|
||||
query = wsme.wsattr([base.Query], default=[])
|
||||
"""The query to find the data for computing statistics.
|
||||
Ownership settings are automatically included based on the Alarm owner.
|
||||
"""
|
||||
|
||||
period = wsme.wsattr(wtypes.IntegerType(minimum=1), default=60)
|
||||
"The time range in seconds over which query"
|
||||
|
||||
comparison_operator = base.AdvEnum('comparison_operator', str,
|
||||
'lt', 'le', 'eq', 'ne', 'ge', 'gt',
|
||||
default='eq')
|
||||
"The comparison against the alarm threshold"
|
||||
|
||||
threshold = wsme.wsattr(float, mandatory=True)
|
||||
"The threshold of the alarm"
|
||||
|
||||
statistic = base.AdvEnum('statistic', str, 'max', 'min', 'avg', 'sum',
|
||||
'count', default='avg')
|
||||
"The statistic to compare to the threshold"
|
||||
|
||||
evaluation_periods = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
||||
"The number of historical periods to evaluate the threshold"
|
||||
|
||||
exclude_outliers = wsme.wsattr(bool, default=False)
|
||||
"Whether datapoints with anomalously low sample counts are excluded"
|
||||
|
||||
ceilometer_sample_api_is_supported = None
|
||||
|
||||
def __init__(self, query=None, **kwargs):
|
||||
query = [base.Query(**q) for q in query] if query else []
|
||||
super(AlarmThresholdRule, self).__init__(query=query, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _check_ceilometer_sample_api(cls):
|
||||
# Check it only once
|
||||
if cls.ceilometer_sample_api_is_supported is None:
|
||||
|
||||
auth_config = pecan.request.cfg.service_credentials
|
||||
client = ceiloclient.get_client(
|
||||
version=2,
|
||||
session=keystone_client.get_session(pecan.request.cfg),
|
||||
# ceiloclient adapter options
|
||||
region_name=auth_config.region_name,
|
||||
interface=auth_config.interface,
|
||||
)
|
||||
try:
|
||||
client.statistics.list(
|
||||
meter_name="idontthinkthatexistsbutwhatever")
|
||||
except Exception as e:
|
||||
if isinstance(e, ceiloexc.HTTPException):
|
||||
if e.code == 410:
|
||||
cls.ceilometer_sample_api_is_supported = False
|
||||
elif e.code < 500:
|
||||
cls.ceilometer_sample_api_is_supported = True
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
# I don't think this meter can exist but how known
|
||||
cls.ceilometer_sample_api_is_supported = True
|
||||
|
||||
if cls.ceilometer_sample_api_is_supported is False:
|
||||
raise base.ClientSideError(
|
||||
"This telemetry installation is not configured to support"
|
||||
"alarm of type 'threshold")
|
||||
|
||||
@staticmethod
|
||||
def validate(threshold_rule):
|
||||
# note(sileht): wsme default doesn't work in some case
|
||||
# workaround for https://bugs.launchpad.net/wsme/+bug/1227039
|
||||
if not threshold_rule.query:
|
||||
threshold_rule.query = []
|
||||
|
||||
# Timestamp is not allowed for AlarmThresholdRule query, as the alarm
|
||||
# evaluator will construct timestamp bounds for the sequence of
|
||||
# statistics queries as the sliding evaluation window advances
|
||||
# over time.
|
||||
v2_utils.validate_query(threshold_rule.query,
|
||||
storage.SampleFilter.__init__,
|
||||
allow_timestamps=False)
|
||||
return threshold_rule
|
||||
|
||||
@classmethod
|
||||
def validate_alarm(cls, alarm):
|
||||
cls._check_ceilometer_sample_api()
|
||||
# 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 '
|
||||
'%(statistic)s of %(threshold)s over %(period)s seconds') %
|
||||
dict(comparison_operator=self.comparison_operator,
|
||||
statistic=self.statistic,
|
||||
threshold=self.threshold,
|
||||
meter_name=self.meter_name,
|
||||
period=self.period))
|
||||
|
||||
def as_dict(self):
|
||||
rule = self.as_dict_from_keys(['period', 'comparison_operator',
|
||||
'threshold', 'statistic',
|
||||
'evaluation_periods', 'meter_name',
|
||||
'exclude_outliers'])
|
||||
rule['query'] = [q.as_dict() for q in self.query]
|
||||
return rule
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(meter_name='cpu_util',
|
||||
period=60,
|
||||
evaluation_periods=1,
|
||||
threshold=300.0,
|
||||
statistic='avg',
|
||||
comparison_operator='gt',
|
||||
query=[{'field': 'resource_id',
|
||||
'value': '2a4d689b-f0b8-49c1-9eef-87cae58d80db',
|
||||
'op': 'eq',
|
||||
'type': 'string'}])
|
@ -21,10 +21,8 @@
|
||||
import datetime
|
||||
import itertools
|
||||
import json
|
||||
import warnings
|
||||
|
||||
import croniter
|
||||
import debtcollector
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import netutils
|
||||
@ -273,13 +271,6 @@ class Alarm(base.Base):
|
||||
|
||||
@staticmethod
|
||||
def validate(alarm):
|
||||
if alarm.type == 'threshold':
|
||||
warnings.simplefilter("always")
|
||||
debtcollector.deprecate(
|
||||
"Ceilometer's API is deprecated as of Ocata. Therefore, "
|
||||
" threshold rule alarms are no longer supported.",
|
||||
version="5.0.0")
|
||||
|
||||
Alarm.check_rule(alarm)
|
||||
Alarm.check_alarm_actions(alarm)
|
||||
|
||||
@ -357,7 +348,7 @@ class Alarm(base.Base):
|
||||
return cls(alarm_id=None,
|
||||
name="SwiftObjectAlarm",
|
||||
description="An alarm",
|
||||
type='threshold',
|
||||
type='gnocchi_aggregation_by_metrics_threshold',
|
||||
time_constraints=[AlarmTimeConstraint.sample().as_dict()],
|
||||
user_id="c96c887c216949acbdfbd8b494863567",
|
||||
project_id="c96c887c216949acbdfbd8b494863567",
|
||||
|
@ -118,7 +118,7 @@ class CompositeEvaluator(evaluator.Evaluator):
|
||||
@property
|
||||
def threshold_evaluators(self):
|
||||
if not self._threshold_evaluators:
|
||||
threshold_types = ('threshold', 'gnocchi_resources_threshold',
|
||||
threshold_types = ('gnocchi_resources_threshold',
|
||||
'gnocchi_aggregation_by_metrics_threshold',
|
||||
'gnocchi_aggregation_by_resources_threshold')
|
||||
self._threshold_evaluators = stevedore.NamedExtensionManager(
|
||||
@ -151,13 +151,16 @@ class CompositeEvaluator(evaluator.Evaluator):
|
||||
alarm_rule['or'])
|
||||
rules_alarm, rules_ok = zip(*rules)
|
||||
return OrOp(rules_alarm), AndOp(rules_ok)
|
||||
else:
|
||||
elif alarm_rule['type'] in self.threshold_evaluators:
|
||||
rule_evaluator = self.threshold_evaluators[alarm_rule['type']].obj
|
||||
self.rule_num += 1
|
||||
name = self.rule_name_prefix + str(self.rule_num)
|
||||
rule = RuleTarget(alarm_rule, rule_evaluator, name)
|
||||
self.rule_targets.append(rule)
|
||||
return AlarmEvaluation(rule), OkEvaluation(rule)
|
||||
else:
|
||||
LOG.error("Invalid rule type: %s" % alarm_rule['type'])
|
||||
return False, False
|
||||
|
||||
def _reason(self, alarm, new_state, rule_target_alarm):
|
||||
transition = alarm.state != new_state
|
||||
|
@ -13,21 +13,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import operator
|
||||
import six
|
||||
|
||||
from ceilometerclient import client as ceiloclient
|
||||
from ceilometerclient import exc as ceiloexc
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from aodh import evaluator
|
||||
from aodh.evaluator import utils
|
||||
from aodh.i18n import _
|
||||
from aodh import keystone_client
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@ -63,86 +57,20 @@ class ThresholdEvaluator(evaluator.Evaluator):
|
||||
# with 'additional_ingestion_lag' seconds if needed.
|
||||
look_back = 1
|
||||
|
||||
def __init__(self, conf):
|
||||
super(ThresholdEvaluator, self).__init__(conf)
|
||||
self._cm_client = None
|
||||
|
||||
@property
|
||||
def cm_client(self):
|
||||
if self._cm_client is None:
|
||||
auth_config = self.conf.service_credentials
|
||||
self._cm_client = ceiloclient.get_client(
|
||||
version=2,
|
||||
session=keystone_client.get_session(self.conf),
|
||||
# ceiloclient adapter options
|
||||
region_name=auth_config.region_name,
|
||||
interface=auth_config.interface,
|
||||
)
|
||||
|
||||
return self._cm_client
|
||||
|
||||
def _bound_duration(self, rule):
|
||||
"""Bound the duration of the statistics query."""
|
||||
now = timeutils.utcnow()
|
||||
# when exclusion of weak datapoints is enabled, we extend
|
||||
# the look-back period so as to allow a clearer sample count
|
||||
# trend to be established
|
||||
look_back = (self.look_back if not rule.get('exclude_outliers')
|
||||
else rule['evaluation_periods'])
|
||||
window = ((rule.get('period', None) or rule['granularity'])
|
||||
* (rule['evaluation_periods'] + look_back) +
|
||||
* (rule['evaluation_periods'] + self.look_back) +
|
||||
self.conf.additional_ingestion_lag)
|
||||
start = now - datetime.timedelta(seconds=window)
|
||||
LOG.debug('query stats from %(start)s to '
|
||||
'%(now)s', {'start': start, 'now': now})
|
||||
return start.isoformat(), now.isoformat()
|
||||
|
||||
@staticmethod
|
||||
def _sanitize(rule, statistics):
|
||||
"""Sanitize statistics."""
|
||||
LOG.debug('sanitize stats %s', statistics)
|
||||
if rule.get('exclude_outliers'):
|
||||
key = operator.attrgetter('count')
|
||||
mean = utils.mean(statistics, key)
|
||||
stddev = utils.stddev(statistics, key, mean)
|
||||
lower = mean - 2 * stddev
|
||||
upper = mean + 2 * stddev
|
||||
inliers, outliers = utils.anomalies(statistics, key, lower, upper)
|
||||
if outliers:
|
||||
LOG.debug('excluded weak datapoints with sample counts %s',
|
||||
[s.count for s in outliers])
|
||||
statistics = inliers
|
||||
else:
|
||||
LOG.debug('no excluded weak datapoints')
|
||||
|
||||
# in practice statistics are always sorted by period start, not
|
||||
# strictly required by the API though
|
||||
statistics = statistics[-rule['evaluation_periods']:]
|
||||
result_statistics = [getattr(stat, rule['statistic'])
|
||||
for stat in statistics]
|
||||
LOG.debug('pruned statistics to %d', len(statistics))
|
||||
return result_statistics
|
||||
|
||||
def _statistics(self, rule, start, end):
|
||||
"""Retrieve statistics over the current window."""
|
||||
after = dict(field='timestamp', op='ge', value=start)
|
||||
before = dict(field='timestamp', op='le', value=end)
|
||||
query = copy.copy(rule['query'])
|
||||
query.extend([before, after])
|
||||
LOG.debug('stats query %s', query)
|
||||
try:
|
||||
return self.cm_client.statistics.list(
|
||||
meter_name=rule['meter_name'], q=query,
|
||||
period=rule['period'])
|
||||
except Exception as e:
|
||||
if isinstance(e, ceiloexc.HTTPException) and e.code == 410:
|
||||
LOG.warning("This telemetry installation is not configured to "
|
||||
"support alarm of type 'threshold', they should "
|
||||
"be disabled or removed.")
|
||||
else:
|
||||
LOG.exception(_('alarm stats retrieval failed'))
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _reason_data(disposition, count, most_recent):
|
||||
"""Create a reason data dictionary for this evaluator type."""
|
||||
|
@ -13,7 +13,6 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import fixtures
|
||||
import mock
|
||||
from oslo_config import fixture
|
||||
from oslotest import base
|
||||
@ -26,10 +25,6 @@ class TestEvaluatorBase(base.BaseTestCase):
|
||||
super(TestEvaluatorBase, self).setUp()
|
||||
conf = service.prepare_service(argv=[], config_files=[])
|
||||
self.conf = self.useFixture(fixture.Config(conf)).conf
|
||||
self.api_client = mock.Mock()
|
||||
self.useFixture(
|
||||
fixtures.MockPatch('ceilometerclient.client.get_client',
|
||||
return_value=self.api_client))
|
||||
self.evaluator = self.EVALUATOR(self.conf)
|
||||
self.notifier = mock.MagicMock()
|
||||
self.evaluator.notifier = self.notifier
|
||||
|
@ -13,7 +13,6 @@
|
||||
"""Tests for aodh/evaluator/composite.py
|
||||
"""
|
||||
|
||||
from ceilometerclient.v2 import statistics
|
||||
import fixtures
|
||||
import mock
|
||||
from oslo_utils import timeutils
|
||||
@ -37,10 +36,6 @@ class BaseCompositeEvaluate(base.TestEvaluatorBase):
|
||||
)).mock.Client.return_value
|
||||
super(BaseCompositeEvaluate, self).setUp()
|
||||
|
||||
@staticmethod
|
||||
def _get_stats(attr, value, count=1):
|
||||
return statistics.Statistics(None, {attr: value, 'count': count})
|
||||
|
||||
@staticmethod
|
||||
def _get_gnocchi_stats(granularity, values):
|
||||
now = timeutils.utcnow_ts()
|
||||
|
@ -47,11 +47,6 @@ Valid threshold alarms are: ``gnocchi_resources_threshold_rule``,
|
||||
``gnocchi_aggregation_by_metrics_threshold_rule``, or
|
||||
``gnocchi_aggregation_by_resources_threshold_rule``.
|
||||
|
||||
.. note::
|
||||
|
||||
As of Ocata, the ``threshold`` alarm is deprecated since Ceilometer's
|
||||
native storage API is deprecated.
|
||||
|
||||
Composite rule alarms
|
||||
---------------------
|
||||
|
||||
|
@ -32,9 +32,6 @@ Alarms
|
||||
.. autotype:: aodh.api.controllers.v2.alarms.Alarm
|
||||
:members:
|
||||
|
||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.threshold.AlarmThresholdRule
|
||||
:members:
|
||||
|
||||
.. autotype:: aodh.api.controllers.v2.alarm_rules.gnocchi.MetricOfResourceRule
|
||||
:members:
|
||||
|
||||
@ -100,6 +97,9 @@ field to specify the rule type, like in the following sample::
|
||||
|
||||
{
|
||||
"threshold": 0.8,
|
||||
"meter_name": "cpu_util",
|
||||
"type": "threshold"
|
||||
"meters": [
|
||||
"f6857d3f-bde6-441a-aa1d-e98fa4ea543f",
|
||||
"ea1491ca-5309-4b5a-9f05-34409c6e8b6c"
|
||||
],
|
||||
"type": "gnocchi_resources_threshold"
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
deprecations:
|
||||
- |
|
||||
The deprecated 'threshold' alarm type has been removed.
|
@ -21,7 +21,6 @@ pecan>=0.8.0
|
||||
oslo.messaging>=5.2.0 # Apache-2.0
|
||||
oslo.middleware>=3.22.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
python-ceilometerclient>=1.5.0
|
||||
python-keystoneclient>=1.6.0
|
||||
pytz>=2013.6
|
||||
requests>=2.5.2
|
||||
|
@ -68,7 +68,6 @@ aodh.storage =
|
||||
sqlite = aodh.storage.impl_sqlalchemy:Connection
|
||||
|
||||
aodh.alarm.rule =
|
||||
threshold = aodh.api.controllers.v2.alarm_rules.threshold:AlarmThresholdRule
|
||||
gnocchi_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:MetricOfResourceRule
|
||||
gnocchi_aggregation_by_metrics_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricsByIdLookupRule
|
||||
gnocchi_aggregation_by_resources_threshold = aodh.api.controllers.v2.alarm_rules.gnocchi:AggregationMetricByResourcesLookupRule
|
||||
@ -76,7 +75,6 @@ aodh.alarm.rule =
|
||||
composite = aodh.api.controllers.v2.alarm_rules.composite:composite_rule
|
||||
|
||||
aodh.evaluator =
|
||||
threshold = aodh.evaluator.threshold:ThresholdEvaluator
|
||||
gnocchi_resources_threshold = aodh.evaluator.gnocchi:GnocchiResourceThresholdEvaluator
|
||||
gnocchi_aggregation_by_metrics_threshold = aodh.evaluator.gnocchi:GnocchiAggregationMetricsThresholdEvaluator
|
||||
gnocchi_aggregation_by_resources_threshold = aodh.evaluator.gnocchi:GnocchiAggregationResourcesThresholdEvaluator
|
||||
|
Loading…
Reference in New Issue
Block a user