Merge "Remove ceilometer-api alarm type"

This commit is contained in:
Zuul 2018-01-16 16:24:47 +00:00 committed by Gerrit Code Review
commit 661017d8ad
12 changed files with 17 additions and 271 deletions

View File

@ -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(

View File

@ -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'}])

View File

@ -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",

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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()

View File

@ -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
---------------------

View File

@ -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"
}

View File

@ -0,0 +1,4 @@
---
deprecations:
- |
The deprecated 'threshold' alarm type has been removed.

View File

@ -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

View File

@ -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