gnocchi: Use Dynamic Aggregates API
Switch to using the Dynamic Aggregates API as the Metric Aggregation
API is deprecated.
When using the Dynamic Aggregates API, any aggregation using rates
can use the underlying base measures for the aggregation rather than
the rate, for example:
(aggregation rate:mean (metric cpu mean))
The tuple of data for each record returned via this API is encapsulated
with information about the aggregation used so adapt the sanitization
function to deal with this and the formatting of the metrics measures
API as well.
Change-Id: I4f631d224404460138f4050b1b981d577b592544
Closes-Bug: 1946793
Depends-On: https://review.opendev.org/863284
(cherry picked from commit 74eadfbd58
)
This commit is contained in:
parent
231f404ad1
commit
e6d55b1ea9
@ -200,10 +200,15 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule):
|
||||
'interface': conf.service_credentials.interface,
|
||||
'region_name': conf.service_credentials.region_name})
|
||||
try:
|
||||
gnocchi_client.metric.aggregation(
|
||||
metrics=rule.metric,
|
||||
query=query,
|
||||
aggregation=rule.aggregation_method,
|
||||
gnocchi_client.aggregates.fetch(
|
||||
operations=[
|
||||
'aggregate', rule.aggregation_method,
|
||||
[
|
||||
'metric', rule.metric,
|
||||
rule.aggregation_method.lstrip('rate:')
|
||||
]
|
||||
],
|
||||
search=query,
|
||||
needed_overlap=0,
|
||||
start="-1 day",
|
||||
stop="now",
|
||||
|
@ -47,6 +47,12 @@ class GnocchiBase(threshold.ThresholdEvaluator):
|
||||
# but not a stddev-of-stddevs).
|
||||
# TODO(sileht): support alarm['exclude_outliers']
|
||||
LOG.debug('sanitize stats %s', statistics)
|
||||
# NOTE(jamespage)
|
||||
# Dynamic Aggregates are returned in a dict struct so
|
||||
# check for this first.
|
||||
if isinstance(statistics, dict):
|
||||
# Pop array of measures from aggregated subdict
|
||||
statistics = statistics['measures']['aggregated']
|
||||
statistics = [stats[VALUE] for stats in statistics
|
||||
if stats[GRANULARITY] == rule['granularity']]
|
||||
if not statistics:
|
||||
@ -93,6 +99,16 @@ class GnocchiResourceThresholdEvaluator(GnocchiBase):
|
||||
class GnocchiAggregationMetricsThresholdEvaluator(GnocchiBase):
|
||||
def _statistics(self, rule, start, end):
|
||||
try:
|
||||
_operations = [
|
||||
'aggregate', rule['aggregation_method']
|
||||
]
|
||||
for metric in rule['metrics']:
|
||||
_operations.append(
|
||||
[
|
||||
'metric', metric,
|
||||
rule['aggregation_method'].lstrip('rate:')
|
||||
]
|
||||
)
|
||||
# FIXME(sileht): In case of a heat autoscaling stack decide to
|
||||
# delete an instance, the gnocchi metrics associated to this
|
||||
# instance will be no more updated and when the alarm will ask
|
||||
@ -101,11 +117,10 @@ class GnocchiAggregationMetricsThresholdEvaluator(GnocchiBase):
|
||||
# So temporary set 'needed_overlap' to 0 to disable the
|
||||
# gnocchi checks about missing points. For more detail see:
|
||||
# https://bugs.launchpad.net/gnocchi/+bug/1479429
|
||||
return self._gnocchi_client.metric.aggregation(
|
||||
metrics=rule['metrics'],
|
||||
return self._gnocchi_client.aggregates.fetch(
|
||||
operations=_operations,
|
||||
granularity=rule['granularity'],
|
||||
start=start, stop=end,
|
||||
aggregation=rule['aggregation_method'],
|
||||
needed_overlap=0)
|
||||
except exceptions.MetricNotFound:
|
||||
raise threshold.InsufficientDataError(
|
||||
@ -128,24 +143,28 @@ class GnocchiAggregationMetricsThresholdEvaluator(GnocchiBase):
|
||||
|
||||
class GnocchiAggregationResourcesThresholdEvaluator(GnocchiBase):
|
||||
def _statistics(self, rule, start, end):
|
||||
# FIXME(sileht): In case of a heat autoscaling stack decide to
|
||||
# delete an instance, the gnocchi metrics associated to this
|
||||
# instance will be no more updated and when the alarm will ask
|
||||
# for the aggregation, gnocchi will raise a 'No overlap'
|
||||
# exception.
|
||||
# So temporary set 'needed_overlap' to 0 to disable the
|
||||
# gnocchi checks about missing points. For more detail see:
|
||||
# https://bugs.launchpad.net/gnocchi/+bug/1479429
|
||||
try:
|
||||
return self._gnocchi_client.metric.aggregation(
|
||||
metrics=rule['metric'],
|
||||
# FIXME(sileht): In case of a heat autoscaling stack decide to
|
||||
# delete an instance, the gnocchi metrics associated to this
|
||||
# instance will be no more updated and when the alarm will ask
|
||||
# for the aggregation, gnocchi will raise a 'No overlap'
|
||||
# exception.
|
||||
# So temporary set 'needed_overlap' to 0 to disable the
|
||||
# gnocchi checks about missing points. For more detail see:
|
||||
# https://bugs.launchpad.net/gnocchi/+bug/1479429
|
||||
return self._gnocchi_client.aggregates.fetch(
|
||||
operations=[
|
||||
'aggregate', rule['aggregation_method'],
|
||||
[
|
||||
'metric', rule['metric'],
|
||||
rule['aggregation_method'].lstrip('rate:')
|
||||
]
|
||||
],
|
||||
granularity=rule['granularity'],
|
||||
query=json.loads(rule['query']),
|
||||
search=json.loads(rule['query']),
|
||||
resource_type=rule["resource_type"],
|
||||
start=start, stop=end,
|
||||
aggregation=rule['aggregation_method'],
|
||||
needed_overlap=0,
|
||||
)
|
||||
needed_overlap=0)
|
||||
except exceptions.MetricNotFound:
|
||||
raise threshold.InsufficientDataError(
|
||||
'metric %s does not exists' % rule['metric'], [])
|
||||
|
@ -2503,14 +2503,16 @@ class TestAlarmsRuleGnocchi(TestAlarmsBase):
|
||||
self.post_json('/alarms', params=json, headers=self.auth_headers)
|
||||
|
||||
self.assertEqual([mock.call(
|
||||
aggregation='count',
|
||||
metrics='ameter',
|
||||
operations=[
|
||||
'aggregate', 'count',
|
||||
['metric', 'ameter', 'count']
|
||||
],
|
||||
needed_overlap=0,
|
||||
start="-1 day",
|
||||
stop="now",
|
||||
query=expected_query,
|
||||
search=expected_query,
|
||||
resource_type="instance")],
|
||||
c.metric.aggregation.mock_calls),
|
||||
c.aggregates.fetch.mock_calls),
|
||||
|
||||
alarms = list(self.alarm_conn.get_alarms(enabled=False))
|
||||
self.assertEqual(1, len(alarms))
|
||||
|
@ -35,8 +35,16 @@ class BaseCompositeEvaluate(base.TestEvaluatorBase):
|
||||
super(BaseCompositeEvaluate, self).setUp()
|
||||
|
||||
@staticmethod
|
||||
def _get_gnocchi_stats(granularity, values):
|
||||
def _get_gnocchi_stats(granularity, values, aggregated=False):
|
||||
now = timeutils.utcnow_ts()
|
||||
if aggregated:
|
||||
return {
|
||||
'measures': {
|
||||
'aggregated':
|
||||
[[str(now - len(values) * granularity),
|
||||
granularity, value] for value in values]
|
||||
}
|
||||
}
|
||||
return [[str(now - len(values) * granularity),
|
||||
granularity, value] for value in values]
|
||||
|
||||
@ -236,7 +244,7 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
|
||||
def test_simple_insufficient(self):
|
||||
self._set_all_alarms('ok')
|
||||
self.client.metric.aggregation.return_value = []
|
||||
self.client.aggregates.fetch.return_value = []
|
||||
self.client.metric.get_measures.return_value = []
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('insufficient data')
|
||||
@ -287,26 +295,36 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
# self.sub_rule4: ok
|
||||
# self.sub_rule5: ok
|
||||
# self.sub_rule6: alarm
|
||||
maxs = self._get_gnocchi_stats(60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(1, 5)])
|
||||
avgs1 = self._get_gnocchi_stats(60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(1, 4)])
|
||||
avgs2 = self._get_gnocchi_stats(60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)])
|
||||
|
||||
gavgs1 = self._get_gnocchi_stats(60, [self.sub_rule4['threshold']
|
||||
- v for v in range(1, 6)])
|
||||
gmaxs = self._get_gnocchi_stats(300, [self.sub_rule5['threshold'] + v
|
||||
for v in range(1, 5)])
|
||||
gavgs2 = self._get_gnocchi_stats(50, [self.sub_rule6['threshold'] + v
|
||||
for v in range(1, 7)])
|
||||
maxs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(1, 5)],
|
||||
aggregated=True)
|
||||
avgs1 = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(1, 4)])
|
||||
avgs2 = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
gavgs1 = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule4['threshold']
|
||||
- v for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
gmaxs = self._get_gnocchi_stats(
|
||||
300, [self.sub_rule5['threshold'] + v
|
||||
for v in range(1, 5)],
|
||||
aggregated=True)
|
||||
gavgs2 = self._get_gnocchi_stats(
|
||||
50, [self.sub_rule6['threshold'] + v
|
||||
for v in range(1, 7)],
|
||||
aggregated=True)
|
||||
|
||||
self.client.metric.get_measures.side_effect = [gavgs1]
|
||||
self.client.metric.aggregation.side_effect = [maxs, avgs1, avgs2,
|
||||
gmaxs, gavgs2]
|
||||
self.client.aggregates.fetch.side_effect = [maxs, avgs1, avgs2,
|
||||
gmaxs, gavgs2]
|
||||
self.evaluator.evaluate(alarm)
|
||||
self.assertEqual(1, self.client.metric.get_measures.call_count)
|
||||
self.assertEqual(5, self.client.metric.aggregation.call_count)
|
||||
self.assertEqual(5, self.client.aggregates.fetch.call_count)
|
||||
self.assertEqual('alarm', alarm.state)
|
||||
expected = mock.call(
|
||||
alarm, 'ok',
|
||||
@ -320,12 +338,14 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
def test_alarm_with_short_circuit_logic(self):
|
||||
alarm = self.alarms[1]
|
||||
# self.sub_rule1: alarm
|
||||
avgs = self._get_gnocchi_stats(60, [self.sub_rule1['threshold'] + v
|
||||
for v in range(1, 6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs]
|
||||
avgs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule1['threshold'] + v
|
||||
for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs]
|
||||
self.evaluator.evaluate(alarm)
|
||||
self.assertEqual('alarm', alarm.state)
|
||||
self.assertEqual(1, self.client.metric.aggregation.call_count)
|
||||
self.assertEqual(1, self.client.aggregates.fetch.call_count)
|
||||
expected = mock.call(self.alarms[1], 'insufficient data',
|
||||
*self._reason(
|
||||
'alarm',
|
||||
@ -336,12 +356,14 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
def test_ok_with_short_circuit_logic(self):
|
||||
alarm = self.alarms[2]
|
||||
# self.sub_rule1: ok
|
||||
avgs = self._get_gnocchi_stats(60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs]
|
||||
avgs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs]
|
||||
self.evaluator.evaluate(alarm)
|
||||
self.assertEqual('ok', alarm.state)
|
||||
self.assertEqual(1, self.client.metric.aggregation.call_count)
|
||||
self.assertEqual(1, self.client.aggregates.fetch.call_count)
|
||||
expected = mock.call(self.alarms[2], 'insufficient data',
|
||||
*self._reason(
|
||||
'ok',
|
||||
@ -351,13 +373,19 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
|
||||
def test_unknown_state_with_sub_rules_trending_state(self):
|
||||
alarm = self.alarms[0]
|
||||
maxs = self._get_gnocchi_stats(60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(-1, 4)])
|
||||
avgs = self._get_gnocchi_stats(60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(-1, 3)])
|
||||
avgs2 = self._get_gnocchi_stats(60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs2, maxs, avgs]
|
||||
maxs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(-1, 4)],
|
||||
aggregated=True)
|
||||
avgs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(-1, 3)],
|
||||
aggregated=True)
|
||||
avgs2 = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs2, maxs, avgs]
|
||||
|
||||
self.evaluator.evaluate(alarm)
|
||||
self.assertEqual('alarm', alarm.state)
|
||||
@ -374,13 +402,19 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
alarm.repeat_actions = True
|
||||
alarm.state = 'ok'
|
||||
|
||||
maxs = self._get_gnocchi_stats(60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(-1, 4)])
|
||||
avgs = self._get_gnocchi_stats(60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(-1, 3)])
|
||||
avgs2 = self._get_gnocchi_stats(60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs2, maxs, avgs]
|
||||
maxs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(-1, 4)],
|
||||
aggregated=True)
|
||||
avgs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(-1, 3)],
|
||||
aggregated=True)
|
||||
avgs2 = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs2, maxs, avgs]
|
||||
|
||||
self.evaluator.evaluate(alarm)
|
||||
self.assertEqual('ok', alarm.state)
|
||||
@ -396,13 +430,19 @@ class CompositeTest(BaseCompositeEvaluate):
|
||||
def test_known_state_with_sub_rules_trending_state_and_not_repeat(self):
|
||||
alarm = self.alarms[2]
|
||||
alarm.state = 'ok'
|
||||
maxs = self._get_gnocchi_stats(60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(-1, 4)])
|
||||
avgs = self._get_gnocchi_stats(60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(-1, 3)])
|
||||
avgs2 = self._get_gnocchi_stats(60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs2, maxs, avgs]
|
||||
maxs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule2['threshold'] + v
|
||||
for v in range(-1, 4)],
|
||||
aggregated=True)
|
||||
avgs = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule3['threshold'] + v
|
||||
for v in range(-1, 3)],
|
||||
aggregated=True)
|
||||
avgs2 = self._get_gnocchi_stats(
|
||||
60, [self.sub_rule1['threshold'] - v
|
||||
for v in range(1, 6)],
|
||||
aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs2, maxs, avgs]
|
||||
self.evaluator.evaluate(alarm)
|
||||
self.assertEqual('ok', alarm.state)
|
||||
self.assertEqual([], self.notifier.notify.mock_calls)
|
||||
|
@ -109,9 +109,9 @@ class TestGnocchiEvaluatorBase(base.TestEvaluatorBase):
|
||||
comparison_operator='gt',
|
||||
threshold=80.0,
|
||||
evaluation_periods=6,
|
||||
aggregation_method='mean',
|
||||
aggregation_method='rate:mean',
|
||||
granularity=50,
|
||||
metric='cpu_util',
|
||||
metric='cpu',
|
||||
resource_type='instance',
|
||||
query='{"=": {"server_group": '
|
||||
'"my_autoscaling_group"}}')
|
||||
@ -121,8 +121,16 @@ class TestGnocchiEvaluatorBase(base.TestEvaluatorBase):
|
||||
super(TestGnocchiEvaluatorBase, self).setUp()
|
||||
|
||||
@staticmethod
|
||||
def _get_stats(granularity, values):
|
||||
def _get_stats(granularity, values, aggregated=False):
|
||||
now = timeutils.utcnow_ts()
|
||||
if aggregated:
|
||||
return {
|
||||
'measures': {
|
||||
'aggregated':
|
||||
[[str(now - len(values) * granularity),
|
||||
granularity, value] for value in values]
|
||||
}
|
||||
}
|
||||
return [[str(now - len(values) * granularity),
|
||||
granularity, value] for value in values]
|
||||
|
||||
@ -431,13 +439,17 @@ class TestGnocchiAggregationMetricsThresholdEvaluate(TestGnocchiEvaluatorBase):
|
||||
self.alarms = self.prepared_alarms[1:2]
|
||||
|
||||
def test_retry_transient_api_failure(self):
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] + v
|
||||
for v in range(4)])
|
||||
self.client.metric.aggregation.side_effect = [Exception('boom'), maxs]
|
||||
maxs = self._get_stats(
|
||||
300,
|
||||
[self.alarms[0].rule['threshold'] + v
|
||||
for v in range(4)],
|
||||
aggregated=True
|
||||
)
|
||||
self.client.aggregates.fetch.side_effect = [Exception('boom'), maxs]
|
||||
self._test_retry_transient()
|
||||
|
||||
def test_simple_insufficient(self):
|
||||
self.client.metric.aggregation.return_value = []
|
||||
self.client.aggregates.fetch.return_value = []
|
||||
self._test_simple_insufficient()
|
||||
|
||||
@mock.patch.object(timeutils, 'utcnow')
|
||||
@ -445,26 +457,33 @@ class TestGnocchiAggregationMetricsThresholdEvaluate(TestGnocchiEvaluatorBase):
|
||||
utcnow.return_value = datetime.datetime(2015, 1, 26, 12, 57, 0, 0)
|
||||
self._set_all_alarms('ok')
|
||||
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(4)])
|
||||
self.client.metric.aggregation.side_effect = [maxs]
|
||||
maxs = self._get_stats(
|
||||
300,
|
||||
[self.alarms[0].rule['threshold'] - v
|
||||
for v in range(4)],
|
||||
aggregated=True
|
||||
)
|
||||
self.client.aggregates.fetch.side_effect = [maxs]
|
||||
self._evaluate_all_alarms()
|
||||
start_alarm = "2015-01-26T12:32:00"
|
||||
end = "2015-01-26T12:57:00"
|
||||
|
||||
self.assertEqual(
|
||||
[mock.call.aggregation(aggregation='max',
|
||||
metrics=[
|
||||
'0bb1604d-1193-4c0a-b4b8-74b170e35e83',
|
||||
'9ddc209f-42f8-41e1-b8f1-8804f59c4053'],
|
||||
granularity=300,
|
||||
needed_overlap=0,
|
||||
start=start_alarm, stop=end)],
|
||||
self.client.metric.mock_calls)
|
||||
[mock.call.fetch(
|
||||
operations=[
|
||||
'aggregate', 'max',
|
||||
['metric', '0bb1604d-1193-4c0a-b4b8-74b170e35e83', 'max'], # noqa
|
||||
['metric', '9ddc209f-42f8-41e1-b8f1-8804f59c4053', 'max'], # noqa
|
||||
],
|
||||
granularity=300,
|
||||
needed_overlap=0,
|
||||
start=start_alarm, stop=end)],
|
||||
self.client.aggregates.mock_calls)
|
||||
self._assert_all_alarms('alarm')
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
maxs = maxs['measures']['aggregated']
|
||||
reason = ('Transition to alarm due to 4 samples outside '
|
||||
'threshold, most recent: %s' % maxs[-1][2])
|
||||
|
||||
@ -475,13 +494,14 @@ class TestGnocchiAggregationMetricsThresholdEvaluate(TestGnocchiEvaluatorBase):
|
||||
def test_simple_alarm_clear(self):
|
||||
self._set_all_alarms('alarm')
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] + v
|
||||
for v in range(1, 5)])
|
||||
self.client.metric.aggregation.side_effect = [maxs]
|
||||
for v in range(1, 5)], aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [maxs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('ok')
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
maxs = maxs['measures']['aggregated']
|
||||
reason = ('Transition to ok due to 4 samples inside '
|
||||
'threshold, most recent: %s' % maxs[-1][2])
|
||||
reason_data = self._reason_data('inside', 4, maxs[-1][2])
|
||||
@ -491,9 +511,13 @@ class TestGnocchiAggregationMetricsThresholdEvaluate(TestGnocchiEvaluatorBase):
|
||||
|
||||
def test_equivocal_from_known_state_ok(self):
|
||||
self._set_all_alarms('ok')
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(-1, 3)])
|
||||
self.client.metric.aggregation.side_effect = [maxs]
|
||||
maxs = self._get_stats(
|
||||
300,
|
||||
[self.alarms[0].rule['threshold'] - v
|
||||
for v in range(-1, 3)],
|
||||
aggregated=True
|
||||
)
|
||||
self.client.aggregates.fetch.side_effect = [maxs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('ok')
|
||||
self.assertEqual(
|
||||
@ -505,18 +529,26 @@ class TestGnocchiAggregationMetricsThresholdEvaluate(TestGnocchiEvaluatorBase):
|
||||
self._set_all_alarms('ok')
|
||||
# NOTE(sileht): we add one useless point (81.0) that will break
|
||||
# the test if the evaluator doesn't remove it.
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(-1, 5)])
|
||||
self.client.metric.aggregation.side_effect = [maxs]
|
||||
maxs = self._get_stats(
|
||||
300,
|
||||
[self.alarms[0].rule['threshold'] - v
|
||||
for v in range(-1, 5)],
|
||||
aggregated=True
|
||||
)
|
||||
self.client.aggregates.fetch.side_effect = [maxs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('alarm')
|
||||
|
||||
def test_equivocal_from_known_state_and_repeat_actions(self):
|
||||
self._set_all_alarms('ok')
|
||||
self.alarms[0].repeat_actions = True
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(-1, 3)])
|
||||
self.client.metric.aggregation.side_effect = [maxs]
|
||||
maxs = self._get_stats(
|
||||
300,
|
||||
[self.alarms[0].rule['threshold'] - v
|
||||
for v in range(-1, 3)],
|
||||
aggregated=True
|
||||
)
|
||||
self.client.aggregates.fetch.side_effect = [maxs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('ok')
|
||||
self.assertEqual([], self.storage_conn.update_alarm.call_args_list)
|
||||
@ -530,9 +562,12 @@ class TestGnocchiAggregationMetricsThresholdEvaluate(TestGnocchiEvaluatorBase):
|
||||
self._set_all_alarms('alarm')
|
||||
self.alarms[0].repeat_actions = True
|
||||
|
||||
maxs = self._get_stats(300, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(4)])
|
||||
self.client.metric.aggregation.side_effect = [maxs]
|
||||
maxs = self._get_stats(
|
||||
300, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(4)],
|
||||
aggregated=True
|
||||
)
|
||||
self.client.aggregates.fetch.side_effect = [maxs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('alarm')
|
||||
self.assertEqual([], self.storage_conn.update_alarm.call_args_list)
|
||||
@ -553,13 +588,13 @@ class TestGnocchiAggregationResourcesThresholdEvaluate(
|
||||
|
||||
def test_retry_transient_api_failure(self):
|
||||
avgs2 = self._get_stats(50, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(6)])
|
||||
self.client.metric.aggregation.side_effect = [
|
||||
for v in range(6)], aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [
|
||||
exceptions.ClientException(500, "error"), avgs2]
|
||||
self._test_retry_transient()
|
||||
|
||||
def test_simple_insufficient(self):
|
||||
self.client.metric.aggregation.return_value = []
|
||||
self.client.aggregates.fetch.return_value = []
|
||||
self._test_simple_insufficient()
|
||||
|
||||
@mock.patch.object(timeutils, 'utcnow')
|
||||
@ -567,25 +602,30 @@ class TestGnocchiAggregationResourcesThresholdEvaluate(
|
||||
utcnow.return_value = datetime.datetime(2015, 1, 26, 12, 57, 0, 0)
|
||||
self._set_all_alarms('ok')
|
||||
avgs = self._get_stats(50, [self.alarms[0].rule['threshold'] + v
|
||||
for v in range(1, 7)])
|
||||
for v in range(1, 7)], aggregated=True)
|
||||
|
||||
self.client.metric.aggregation.side_effect = [avgs]
|
||||
self.client.aggregates.fetch.side_effect = [avgs]
|
||||
self._evaluate_all_alarms()
|
||||
start_alarm = "2015-01-26T12:51:10"
|
||||
end = "2015-01-26T12:57:00"
|
||||
self.assertEqual(
|
||||
[mock.call.aggregation(aggregation='mean', metrics='cpu_util',
|
||||
granularity=50,
|
||||
needed_overlap=0,
|
||||
query={"=": {"server_group":
|
||||
"my_autoscaling_group"}},
|
||||
resource_type='instance',
|
||||
start=start_alarm, stop=end)],
|
||||
self.client.metric.mock_calls)
|
||||
[mock.call.fetch(
|
||||
operations=[
|
||||
'aggregate', 'rate:mean',
|
||||
['metric', 'cpu', 'mean'],
|
||||
],
|
||||
granularity=50,
|
||||
search={"=": {"server_group":
|
||||
"my_autoscaling_group"}},
|
||||
resource_type='instance',
|
||||
start=start_alarm, stop=end,
|
||||
needed_overlap=0)],
|
||||
self.client.aggregates.mock_calls)
|
||||
self._assert_all_alarms('alarm')
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
avgs = avgs['measures']['aggregated']
|
||||
reason = ('Transition to alarm due to 6 samples outside '
|
||||
'threshold, most recent: %s' % avgs[-1][2])
|
||||
reason_data = self._reason_data('outside', 6, avgs[-1][2])
|
||||
@ -595,13 +635,14 @@ class TestGnocchiAggregationResourcesThresholdEvaluate(
|
||||
def test_simple_alarm_clear(self):
|
||||
self._set_all_alarms('alarm')
|
||||
avgs = self._get_stats(50, [self.alarms[0].rule['threshold'] - v
|
||||
for v in range(6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs]
|
||||
for v in range(6)], aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('ok')
|
||||
expected = [mock.call(alarm) for alarm in self.alarms]
|
||||
update_calls = self.storage_conn.update_alarm.call_args_list
|
||||
self.assertEqual(expected, update_calls)
|
||||
avgs = avgs['measures']['aggregated']
|
||||
reason = ('Transition to ok due to 6 samples inside '
|
||||
'threshold, most recent: %s' % avgs[-1][2])
|
||||
reason_data = self._reason_data('inside', 6, avgs[-1][2])
|
||||
@ -611,8 +652,8 @@ class TestGnocchiAggregationResourcesThresholdEvaluate(
|
||||
def test_equivocal_from_known_state_ok(self):
|
||||
self._set_all_alarms('ok')
|
||||
avgs = self._get_stats(50, [self.alarms[0].rule['threshold'] + v
|
||||
for v in range(6)])
|
||||
self.client.metric.aggregation.side_effect = [avgs]
|
||||
for v in range(6)], aggregated=True)
|
||||
self.client.aggregates.fetch.side_effect = [avgs]
|
||||
self._evaluate_all_alarms()
|
||||
self._assert_all_alarms('ok')
|
||||
self.assertEqual(
|
||||
|
Loading…
Reference in New Issue
Block a user