Support threshold type alarm again
Add threshold type alarm back. Gnocchi is not actively maintained currently but there are still users running Ceilometer in production and relying on Ceilometer for auditing and billing. Change-Id: I94ea998affbdd9f5535431f3ba713e2d4662b253
This commit is contained in:
parent
ec0ee160cf
commit
428e394e5a
@ -184,6 +184,158 @@ class AodhClientTest(base.ClientTestBase):
|
||||
self.assertNotIn(ALARM_ID,
|
||||
[r['alarm_id'] for r in self.parser.listing(result)])
|
||||
|
||||
def test_threshold_scenario(self):
|
||||
PROJECT_ID = uuidutils.generate_uuid()
|
||||
|
||||
# CREATE
|
||||
result = self.aodh(u'alarm',
|
||||
params=(u"create --type threshold --name "
|
||||
"test_threshold_scenario "
|
||||
"-m meter_name --threshold 5 "
|
||||
"--project-id %s" % PROJECT_ID))
|
||||
alarm = self.details_multiple(result)[0]
|
||||
ALARM_ID = alarm['alarm_id']
|
||||
self.assertEqual('test_threshold_scenario', alarm['name'])
|
||||
self.assertEqual('meter_name', alarm['meter_name'])
|
||||
self.assertEqual('5.0', alarm['threshold'])
|
||||
|
||||
# CREATE WITH --TIME-CONSTRAINT
|
||||
result = self.aodh(
|
||||
u'alarm',
|
||||
params=(u"create --type threshold --name alarm_tc "
|
||||
"-m meter_name --threshold 5 "
|
||||
"--time-constraint "
|
||||
"name=cons1;start='0 11 * * *';duration=300 "
|
||||
"--time-constraint "
|
||||
"name=cons2;start='0 23 * * *';duration=600 "
|
||||
"--project-id %s" % PROJECT_ID))
|
||||
alarm = self.details_multiple(result)[0]
|
||||
self.assertEqual('alarm_tc', alarm['name'])
|
||||
self.assertEqual('meter_name', alarm['meter_name'])
|
||||
self.assertEqual('5.0', alarm['threshold'])
|
||||
self.assertIsNotNone(alarm['time_constraints'])
|
||||
|
||||
# CREATE FAIL MISSING PARAM
|
||||
self.assertRaises(exceptions.CommandFailed,
|
||||
self.aodh, u'alarm',
|
||||
params=(u"create --type threshold --name "
|
||||
"test_threshold_scenario "
|
||||
"--project-id %s" % PROJECT_ID))
|
||||
|
||||
# UPDATE
|
||||
result = self.aodh(
|
||||
'alarm', params=("update %s --severity critical --threshold 10"
|
||||
% ALARM_ID))
|
||||
alarm_updated = self.details_multiple(result)[0]
|
||||
self.assertEqual(ALARM_ID, alarm_updated["alarm_id"])
|
||||
self.assertEqual('critical', alarm_updated['severity'])
|
||||
self.assertEqual('10.0', alarm_updated["threshold"])
|
||||
|
||||
# GET
|
||||
result = self.aodh(
|
||||
'alarm', params="show %s" % ALARM_ID)
|
||||
alarm_show = self.details_multiple(result)[0]
|
||||
self.assertEqual(ALARM_ID, alarm_show["alarm_id"])
|
||||
self.assertEqual(PROJECT_ID, alarm_show["project_id"])
|
||||
self.assertEqual('test_threshold_scenario', alarm_show['name'])
|
||||
self.assertEqual('meter_name', alarm_show['meter_name'])
|
||||
self.assertEqual('10.0', alarm_show['threshold'])
|
||||
|
||||
# GET BY NAME
|
||||
result = self.aodh(
|
||||
'alarm', params="show --name test_threshold_scenario")
|
||||
alarm_show = self.details_multiple(result)[0]
|
||||
self.assertEqual(ALARM_ID, alarm_show["alarm_id"])
|
||||
self.assertEqual(PROJECT_ID, alarm_show["project_id"])
|
||||
self.assertEqual('test_threshold_scenario', alarm_show['name'])
|
||||
self.assertEqual('meter_name', alarm_show['meter_name'])
|
||||
self.assertEqual('10.0', alarm_show['threshold'])
|
||||
|
||||
# GET BY NAME AND ID ERROR
|
||||
self.assertRaises(exceptions.CommandFailed,
|
||||
self.aodh, u'alarm',
|
||||
params=(u"show %s --name test_threshold_scenario" %
|
||||
ALARM_ID))
|
||||
|
||||
# LIST
|
||||
result = self.aodh('alarm', params="list")
|
||||
self.assertIn(ALARM_ID,
|
||||
[r['alarm_id'] for r in self.parser.listing(result)])
|
||||
output_colums = ['alarm_id', 'type', 'name', 'state', 'severity',
|
||||
'enabled']
|
||||
for alarm_list in self.parser.listing(result):
|
||||
self.assertEqual(sorted(output_colums), sorted(alarm_list.keys()))
|
||||
if alarm_list["alarm_id"] == ALARM_ID:
|
||||
self.assertEqual('test_threshold_scenario', alarm_list['name'])
|
||||
|
||||
# LIST WITH PAGINATION
|
||||
# list with limit
|
||||
result = self.aodh('alarm',
|
||||
params="list --limit 1")
|
||||
alarm_list = self.parser.listing(result)
|
||||
self.assertEqual(1, len(alarm_list))
|
||||
# list with sort with key=name dir=asc
|
||||
result = self.aodh('alarm',
|
||||
params="list --sort name:asc")
|
||||
names = [r['name'] for r in self.parser.listing(result)]
|
||||
sorted_name = sorted(names)
|
||||
self.assertEqual(sorted_name, names)
|
||||
# list with sort with key=name dir=asc and key=alarm_id dir=asc
|
||||
result = self.aodh(u'alarm',
|
||||
params=(u"create --type threshold --name "
|
||||
"test_threshold_scenario "
|
||||
"-m meter_name --threshold 5 "
|
||||
"--project-id %s" % PROJECT_ID))
|
||||
created_alarm_id = self.details_multiple(result)[0]['alarm_id']
|
||||
result = self.aodh('alarm',
|
||||
params="list --sort name:asc --sort alarm_id:asc")
|
||||
alarm_list = self.parser.listing(result)
|
||||
ids_with_same_name = []
|
||||
names = []
|
||||
for alarm in alarm_list:
|
||||
names.append(['alarm_name'])
|
||||
if alarm['name'] == 'test_threshold_scenario':
|
||||
ids_with_same_name.append(alarm['alarm_id'])
|
||||
sorted_ids = sorted(ids_with_same_name)
|
||||
sorted_names = sorted(names)
|
||||
self.assertEqual(sorted_names, names)
|
||||
self.assertEqual(sorted_ids, ids_with_same_name)
|
||||
# list with sort with key=name dir=desc and with the marker equal to
|
||||
# the alarm_id of the test_threshold_scenario we created for this test.
|
||||
result = self.aodh('alarm',
|
||||
params="list --sort name:desc "
|
||||
"--marker %s" % created_alarm_id)
|
||||
self.assertIn('alarm_tc',
|
||||
[r['name'] for r in self.parser.listing(result)])
|
||||
self.aodh('alarm', params="delete %s" % created_alarm_id)
|
||||
|
||||
# LIST WITH QUERY
|
||||
result = self.aodh('alarm',
|
||||
params=("list --query project_id=%s" % PROJECT_ID))
|
||||
alarm_list = self.parser.listing(result)[0]
|
||||
self.assertEqual(ALARM_ID, alarm_list["alarm_id"])
|
||||
self.assertEqual('test_threshold_scenario', alarm_list['name'])
|
||||
|
||||
# DELETE
|
||||
result = self.aodh('alarm', params="delete %s" % ALARM_ID)
|
||||
self.assertEqual("", result)
|
||||
|
||||
# GET FAIL
|
||||
result = self.aodh('alarm', params="show %s" % ALARM_ID,
|
||||
fail_ok=True, merge_stderr=True)
|
||||
expected = "Alarm %s not found (HTTP 404)" % ALARM_ID
|
||||
self.assertFirstLineStartsWith(result.splitlines(), expected)
|
||||
|
||||
# DELETE FAIL
|
||||
result = self.aodh('alarm', params="delete %s" % ALARM_ID,
|
||||
fail_ok=True, merge_stderr=True)
|
||||
self.assertFirstLineStartsWith(result.splitlines(), expected)
|
||||
|
||||
# LIST DOES NOT HAVE ALARM
|
||||
result = self.aodh('alarm', params="list")
|
||||
self.assertNotIn(ALARM_ID,
|
||||
[r['alarm_id'] for r in self.parser.listing(result)])
|
||||
|
||||
def test_composite_scenario(self):
|
||||
|
||||
project_id = uuidutils.generate_uuid()
|
||||
@ -298,6 +450,25 @@ class AodhClientTest(base.ClientTestBase):
|
||||
test(params)
|
||||
self.aodh('alarm', params='delete %s' % alarm['alarm_id'])
|
||||
|
||||
def test_threshold_alarm_create_show_query(self):
|
||||
params = ('create --type threshold --name alarm-multiple-query '
|
||||
'-m cpu_util --threshold 90 --query "project_id=123;'
|
||||
'resource_id=456"')
|
||||
expected_lines = {
|
||||
'query': 'project_id = 123 AND',
|
||||
'': 'resource_id = 456'
|
||||
}
|
||||
self._test_alarm_create_show_query(params, expected_lines)
|
||||
|
||||
params = ('create --type threshold --name alarm-single-query '
|
||||
'-m cpu_util --threshold 90 --query project_id=123')
|
||||
expected_lines = {'query': 'project_id = 123'}
|
||||
self._test_alarm_create_show_query(params, expected_lines)
|
||||
|
||||
params = ('create --type threshold --name alarm-no-query '
|
||||
'-m cpu_util --threshold 90')
|
||||
self._test_alarm_create_show_query(params, {'query': ''})
|
||||
|
||||
def test_event_alarm_create_show_query(self):
|
||||
params = ('create --type event --name alarm-multiple-query '
|
||||
'--query "traits.project_id=789;traits.resource_id=012"')
|
||||
|
@ -49,6 +49,22 @@ class CliAlarmCreateTest(testtools.TestCase):
|
||||
'--threshold, --resource-id, --resource-type and '
|
||||
'--aggregation-method')
|
||||
|
||||
@mock.patch.object(argparse.ArgumentParser, 'error')
|
||||
def test_validate_args_threshold(self, mock_arg):
|
||||
# Cover the test case of the method _validate_args for
|
||||
# threshold
|
||||
parser = self.cli_alarm_create.get_parser('aodh alarm create')
|
||||
test_parsed_args = parser.parse_args([
|
||||
'--name', 'threshold_test',
|
||||
'--type', 'threshold',
|
||||
'--threshold', '80'
|
||||
])
|
||||
self.cli_alarm_create._validate_args(test_parsed_args)
|
||||
mock_arg.assert_called_once_with(
|
||||
'Threshold alarm requires -m/--meter-name and '
|
||||
'--threshold parameters. Meter name can be '
|
||||
'found in Ceilometer')
|
||||
|
||||
@mock.patch.object(argparse.ArgumentParser, 'error')
|
||||
def test_validate_args_composite(self, mock_arg):
|
||||
# Cover the test case of the method _validate_args for
|
||||
@ -156,6 +172,13 @@ class CliAlarmCreateTest(testtools.TestCase):
|
||||
'type': '',
|
||||
'value': 'fake-resource-id'}]
|
||||
},
|
||||
'threshold_rule': {'comparison_operator': 'le',
|
||||
'evaluation_periods': 60,
|
||||
'query': [{'field': 'resource',
|
||||
'op': 'eq',
|
||||
'type': '',
|
||||
'value': 'fake-resource-id'}],
|
||||
'threshold': 80.0},
|
||||
'gnocchi_resources_threshold_rule': {
|
||||
'granularity': '60',
|
||||
'metric': 'cpu',
|
||||
|
@ -115,7 +115,10 @@ class AlarmManager(base.Manager):
|
||||
else:
|
||||
self._clean_rules(alarm_update['type'], alarm_update)
|
||||
|
||||
if 'event_rule' in alarm_update:
|
||||
if 'threshold_rule' in alarm_update:
|
||||
alarm['threshold_rule'].update(alarm_update.get('threshold_rule'))
|
||||
alarm_update.pop('threshold_rule')
|
||||
elif 'event_rule' in alarm_update:
|
||||
if ('type' in alarm_update and
|
||||
alarm_update['type'] != alarm['type']):
|
||||
alarm.pop('%s_rule' % alarm['type'], None)
|
||||
|
@ -24,7 +24,7 @@ from aodhclient import exceptions
|
||||
from aodhclient.i18n import _
|
||||
from aodhclient import utils
|
||||
|
||||
ALARM_TYPES = ['event', 'composite',
|
||||
ALARM_TYPES = ['event', 'composite', 'threshold',
|
||||
'gnocchi_resources_threshold',
|
||||
'gnocchi_aggregation_by_metrics_threshold',
|
||||
'gnocchi_aggregation_by_resources_threshold',
|
||||
@ -33,7 +33,7 @@ ALARM_STATES = ['ok', 'alarm', 'insufficient data']
|
||||
ALARM_SEVERITY = ['low', 'moderate', 'critical']
|
||||
ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt']
|
||||
ALARM_OP_MAP = dict(zip(ALARM_OPERATORS, ('<', '<=', '=', '!=', '>=', '>')))
|
||||
|
||||
STATISTICS = ['max', 'min', 'avg', 'sum', 'count']
|
||||
ALARM_LIST_COLS = ['alarm_id', 'type', 'name', 'state', 'severity', 'enabled']
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ def _format_alarm(alarm):
|
||||
alarm["time_constraints"] = jsonutils.dumps(alarm["time_constraints"],
|
||||
sort_keys=True,
|
||||
indent=2)
|
||||
# only works for event alarm
|
||||
# only works for threshold and event alarm
|
||||
if isinstance(alarm.get('query'), list):
|
||||
query_rows = []
|
||||
for q in alarm['query']:
|
||||
@ -265,7 +265,7 @@ class CliAlarmCreate(show.ShowOne):
|
||||
common_group = parser.add_argument_group('common alarm rules')
|
||||
common_group.add_argument(
|
||||
'--query', metavar='<QUERY>', dest='query',
|
||||
help="For alarms of type event: "
|
||||
help="For alarms of type threshold or event: "
|
||||
"key[op]data_type::value; list. data_type is optional, "
|
||||
"but if supplied must be string, integer, float, or boolean. "
|
||||
'For alarms of '
|
||||
@ -284,11 +284,26 @@ class CliAlarmCreate(show.ShowOne):
|
||||
'--threshold', type=float, metavar='<THRESHOLD>',
|
||||
dest='threshold', help='Threshold to evaluate against.')
|
||||
|
||||
# For event type alarm
|
||||
event_group = parser.add_argument_group('event alarm')
|
||||
event_group.add_argument(
|
||||
'--event-type', metavar='<EVENT_TYPE>',
|
||||
dest='event_type', help='Event type to evaluate against')
|
||||
|
||||
# For Ceilometer threshold type alarm
|
||||
threshold_group = parser.add_argument_group('threshold alarm')
|
||||
threshold_group.add_argument(
|
||||
'-m', '--meter-name', metavar='<METER NAME>',
|
||||
dest='meter_name', help='Meter to evaluate against')
|
||||
threshold_group.add_argument(
|
||||
'--period', type=int, metavar='<PERIOD>', dest='period',
|
||||
help='Length of each period (seconds) to evaluate over.')
|
||||
threshold_group.add_argument(
|
||||
'--statistic', metavar='<STATISTIC>', dest='statistic',
|
||||
choices=STATISTICS,
|
||||
help='Statistic to evaluate, one of: ' + str(STATISTICS))
|
||||
|
||||
# For common Gnocchi threshold type alarm
|
||||
gnocchi_common_group = parser.add_argument_group(
|
||||
'common gnocchi alarm rules')
|
||||
gnocchi_common_group.add_argument(
|
||||
@ -313,15 +328,19 @@ class CliAlarmCreate(show.ShowOne):
|
||||
'--resource-id', metavar='<RESOURCE_ID>',
|
||||
dest='resource_id', help='The id of a resource.')
|
||||
|
||||
# For composite type alarm
|
||||
composite_group = parser.add_argument_group('composite alarm')
|
||||
composite_group.add_argument(
|
||||
'--composite-rule', metavar='<COMPOSITE_RULE>',
|
||||
dest='composite_rule',
|
||||
type=jsonutils.loads,
|
||||
help='Composite threshold rule with JSON format, the form can '
|
||||
'be a nested dict which combine gnocchi rules by '
|
||||
'be a nested dict which combine threshold/gnocchi rules by '
|
||||
'"and", "or". For example, the form is like: '
|
||||
'{"or":[RULE1, RULE2, {"and": [RULE3, RULE4]}]}.'
|
||||
'{"or":[RULE1, RULE2, {"and": [RULE3, RULE4]}]}, The '
|
||||
'RULEx can be basic threshold rules but must include a '
|
||||
'"type" field, like this: {"threshold": 0.8,'
|
||||
'"meter_name":"cpu_util","type":"threshold"}'
|
||||
)
|
||||
|
||||
loadbalancer_member_health_group = parser.add_argument_group(
|
||||
@ -366,7 +385,12 @@ class CliAlarmCreate(show.ShowOne):
|
||||
raise argparse.ArgumentTypeError(msg)
|
||||
|
||||
def _validate_args(self, parsed_args):
|
||||
if (parsed_args.type == 'gnocchi_resources_threshold' and
|
||||
if (parsed_args.type == 'threshold' and
|
||||
not (parsed_args.meter_name and parsed_args.threshold)):
|
||||
self.parser.error('Threshold alarm requires -m/--meter-name and '
|
||||
'--threshold parameters. Meter name can be '
|
||||
'found in Ceilometer')
|
||||
elif (parsed_args.type == 'gnocchi_resources_threshold' and
|
||||
not (parsed_args.metrics and parsed_args.threshold is not None
|
||||
and parsed_args.resource_id and parsed_args.resource_type
|
||||
and parsed_args.aggregation_method)):
|
||||
@ -408,8 +432,12 @@ class CliAlarmCreate(show.ShowOne):
|
||||
'alarm_actions', 'ok_actions',
|
||||
'insufficient_data_actions',
|
||||
'time_constraints', 'repeat_actions'])
|
||||
if parsed_args.type == 'event' and parsed_args.query:
|
||||
if parsed_args.type in ('threshold', 'event') and parsed_args.query:
|
||||
parsed_args.query = utils.cli_to_array(parsed_args.query)
|
||||
alarm['threshold_rule'] = utils.dict_from_parsed_args(
|
||||
parsed_args, ['meter_name', 'period', 'evaluation_periods',
|
||||
'statistic', 'comparison_operator', 'threshold',
|
||||
'query'])
|
||||
alarm['event_rule'] = utils.dict_from_parsed_args(
|
||||
parsed_args, ['event_type', 'query'])
|
||||
alarm['gnocchi_resources_threshold_rule'] = (
|
||||
|
@ -64,25 +64,34 @@ command.
|
||||
Examples
|
||||
--------
|
||||
|
||||
Create an alarm::
|
||||
Create a Ceilometer threshold alarm::
|
||||
|
||||
aodh alarm create -t gnocchi_resources_threshold --name alarm1 \
|
||||
openstack alarm create --name alarm1 --description 'CPU High Average' \
|
||||
--type threshold --meter-name cpu_util \
|
||||
--threshold 5 --comparison-operator gt --statistic avg \
|
||||
--period 60 --evaluation-periods 3 \
|
||||
--query "metadata.user_metadata.stack=$heat_stack_id" \
|
||||
--alarm-action 'log://'
|
||||
|
||||
Create a Gnocchi threshold alarm::
|
||||
|
||||
openstack alarm create -t gnocchi_resources_threshold --name alarm1 \
|
||||
--metric cpu_util --threshold 5 --resource_id <RES_ID> \
|
||||
--resource_type generic --aggregation_method mean --project-id <PROJ_ID>
|
||||
|
||||
List alarms::
|
||||
|
||||
aodh alarm list
|
||||
openstack alarm list
|
||||
|
||||
List alarm with query parameters::
|
||||
|
||||
aodh alarm list --query "state=alarm and type=gnocchi_resources_threshold"
|
||||
openstack alarm list --query "state=alarm and type=gnocchi_resources_threshold"
|
||||
|
||||
Show an alarm's history::
|
||||
|
||||
aodh alarm-history show <ALARM_ID>
|
||||
openstack alarm-history show <ALARM_ID>
|
||||
|
||||
Search alarm history data::
|
||||
|
||||
aodh alarm-history search --query 'timestamp>"2016-03-09T01:22:35"'
|
||||
openstack alarm-history search --query 'timestamp>"2016-03-09T01:22:35"'
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- Add threshold type alarm back. Gnocchi is not actively maintained currently
|
||||
but there are still users running Ceilometer in production and relying on
|
||||
Ceilometer for auditing and billing.
|
Loading…
x
Reference in New Issue
Block a user