From b53d862bb5f9fc6528d13de3006f8bb0f66d14f2 Mon Sep 17 00:00:00 2001 From: Mehdi Abaakouk Date: Thu, 11 Jan 2018 10:18:07 +0100 Subject: [PATCH] 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 --- .../controllers/v2/alarm_rules/composite.py | 3 +- .../controllers/v2/alarm_rules/threshold.py | 161 ------------------ aodh/api/controllers/v2/alarms.py | 11 +- aodh/evaluator/composite.py | 7 +- aodh/evaluator/threshold.py | 74 +------- aodh/tests/unit/evaluator/base.py | 5 - aodh/tests/unit/evaluator/test_composite.py | 5 - doc/source/admin/telemetry-alarms.rst | 5 - doc/source/contributor/webapi/v2.rst | 10 +- ...move-threshold-alarm-a7901991d2da09f2.yaml | 4 + requirements.txt | 1 - setup.cfg | 2 - 12 files changed, 17 insertions(+), 271 deletions(-) delete mode 100644 aodh/api/controllers/v2/alarm_rules/threshold.py create mode 100644 releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml diff --git a/aodh/api/controllers/v2/alarm_rules/composite.py b/aodh/api/controllers/v2/alarm_rules/composite.py index 5062a899a..21092760f 100644 --- a/aodh/api/controllers/v2/alarm_rules/composite.py +++ b/aodh/api/controllers/v2/alarm_rules/composite.py @@ -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( diff --git a/aodh/api/controllers/v2/alarm_rules/threshold.py b/aodh/api/controllers/v2/alarm_rules/threshold.py deleted file mode 100644 index 5c30d28e3..000000000 --- a/aodh/api/controllers/v2/alarm_rules/threshold.py +++ /dev/null @@ -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'}]) diff --git a/aodh/api/controllers/v2/alarms.py b/aodh/api/controllers/v2/alarms.py index ceb3931d8..60e62eddf 100644 --- a/aodh/api/controllers/v2/alarms.py +++ b/aodh/api/controllers/v2/alarms.py @@ -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", diff --git a/aodh/evaluator/composite.py b/aodh/evaluator/composite.py index bf1de7750..f7f140078 100644 --- a/aodh/evaluator/composite.py +++ b/aodh/evaluator/composite.py @@ -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 diff --git a/aodh/evaluator/threshold.py b/aodh/evaluator/threshold.py index 884714b1c..cb9f8071e 100644 --- a/aodh/evaluator/threshold.py +++ b/aodh/evaluator/threshold.py @@ -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.""" diff --git a/aodh/tests/unit/evaluator/base.py b/aodh/tests/unit/evaluator/base.py index 047be2b81..1363819b4 100644 --- a/aodh/tests/unit/evaluator/base.py +++ b/aodh/tests/unit/evaluator/base.py @@ -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 diff --git a/aodh/tests/unit/evaluator/test_composite.py b/aodh/tests/unit/evaluator/test_composite.py index 1d37ba93d..84ae34fb6 100644 --- a/aodh/tests/unit/evaluator/test_composite.py +++ b/aodh/tests/unit/evaluator/test_composite.py @@ -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() diff --git a/doc/source/admin/telemetry-alarms.rst b/doc/source/admin/telemetry-alarms.rst index 87f55f4dd..4c16be77e 100644 --- a/doc/source/admin/telemetry-alarms.rst +++ b/doc/source/admin/telemetry-alarms.rst @@ -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 --------------------- diff --git a/doc/source/contributor/webapi/v2.rst b/doc/source/contributor/webapi/v2.rst index 6fffc6ee3..601b217b6 100644 --- a/doc/source/contributor/webapi/v2.rst +++ b/doc/source/contributor/webapi/v2.rst @@ -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" } diff --git a/releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml b/releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml new file mode 100644 index 000000000..4c793c5b1 --- /dev/null +++ b/releasenotes/notes/remove-threshold-alarm-a7901991d2da09f2.yaml @@ -0,0 +1,4 @@ +--- +deprecations: + - | + The deprecated 'threshold' alarm type has been removed. diff --git a/requirements.txt b/requirements.txt index 362c6b6f4..d5eb10fa8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/setup.cfg b/setup.cfg index 250d52992..61dde4d62 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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