From 8218e5d53dc34b39f607726dc2f5fd270f2bda86 Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Mon, 14 Mar 2016 19:17:57 +0800 Subject: [PATCH] Add name support for show, update and delete Since the alarm name is limited to be unique for each project, add support for users to show, update and delete alarms using alarm name will be much more user friendly than using alarm_id. Partial-Bug: #1556809 Change-Id: I2d9316a24372f28b65c15a02aaefda8c0a1f748b --- aodhclient/exceptions.py | 8 ++ aodhclient/tests/functional/test_alarm.py | 95 +++++++++++++++++++---- aodhclient/v2/alarm_cli.py | 81 +++++++++++++++++-- 3 files changed, 162 insertions(+), 22 deletions(-) diff --git a/aodhclient/exceptions.py b/aodhclient/exceptions.py index a0999a0..01645d2 100644 --- a/aodhclient/exceptions.py +++ b/aodhclient/exceptions.py @@ -47,6 +47,10 @@ class MutipleMeaningException(object): """An mixin for exception that can be enhanced by reading the details""" +class CommandError(Exception): + pass + + class BadRequest(ClientException): """HTTP 400 - Bad request: you sent some malformed data.""" http_status = 400 @@ -110,6 +114,10 @@ class RateLimit(RetryAfterException): message = "Rate limit" +class NoUniqueMatch(Exception): + pass + + class NotImplemented(ClientException): """HTTP 501 - Not Implemented: diff --git a/aodhclient/tests/functional/test_alarm.py b/aodhclient/tests/functional/test_alarm.py index 55bfa6c..311ec38 100644 --- a/aodhclient/tests/functional/test_alarm.py +++ b/aodhclient/tests/functional/test_alarm.py @@ -73,6 +73,21 @@ class AodhClientTest(base.ClientTestBase): self.assertEqual('ev_alarm1', alarm_show['name']) self.assertEqual('dummy', alarm_show['event_type']) + # GET BY NAME + result = self.aodh( + 'alarm', params="show --alarm-name ev_alarm1") + 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('ev_alarm1', alarm_show['name']) + self.assertEqual('dummy', alarm_show['event_type']) + + # GET BY NAME AND ID ERROR + self.assertRaises(exceptions.CommandFailed, + self.aodh, u'alarm', + params=(u"show %s --alarm-name ev_alarm1" % + ALARM_ID)) + # LIST result = self.aodh('alarm', params="list") self.assertIn(ALARM_ID, @@ -117,12 +132,12 @@ class AodhClientTest(base.ClientTestBase): # CREATE result = self.aodh(u'alarm', - params=(u"create --type threshold --name alarm1 " + params=(u"create --type threshold --name alarm_th " "-m meter_name --threshold 5 " "--project-id %s" % PROJECT_ID)) alarm = self.details_multiple(result)[0] ALARM_ID = alarm['alarm_id'] - self.assertEqual('alarm1', alarm['name']) + self.assertEqual('alarm_th', alarm['name']) self.assertEqual('meter_name', alarm['meter_name']) self.assertEqual('5.0', alarm['threshold']) @@ -144,17 +159,18 @@ class AodhClientTest(base.ClientTestBase): # CREATE FAIL result = self.aodh(u'alarm', - params=(u"create --type threshold --name alarm1 " + params=(u"create --type threshold --name alarm_th " "-m meter_name --threshold 5 " "--project-id %s" % PROJECT_ID), fail_ok=True, merge_stderr=True) self.assertFirstLineStartsWith( - result.split('\n'), "Alarm with name='alarm1' exists (HTTP 409)") + result.split('\n'), + "Alarm with name='alarm_th' exists (HTTP 409)") # CREATE FAIL MISSING PARAM self.assertRaises(exceptions.CommandFailed, self.aodh, u'alarm', - params=(u"create --type threshold --name alarm1 " + params=(u"create --type threshold --name alarm_th " "--project-id %s" % PROJECT_ID)) # UPDATE @@ -172,10 +188,26 @@ class AodhClientTest(base.ClientTestBase): 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('alarm1', alarm_show['name']) + self.assertEqual('alarm_th', 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 --alarm-name alarm_th") + 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('alarm_th', 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 --alarm-name alarm_th" % + ALARM_ID)) + # LIST result = self.aodh('alarm', params="list") self.assertIn(ALARM_ID, @@ -185,14 +217,14 @@ class AodhClientTest(base.ClientTestBase): 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('alarm1', alarm_list['name']) + self.assertEqual('alarm_th', alarm_list['name']) # 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('alarm1', alarm_list['name']) + self.assertEqual('alarm_th', alarm_list['name']) # DELETE result = self.aodh('alarm', params="delete %s" % ALARM_ID) @@ -272,6 +304,20 @@ class AodhClientTest(base.ClientTestBase): self.assertEqual(project_id, alarm_show["project_id"]) self.assertEqual('calarm1', alarm_show['name']) + # GET BY NAME + result = self.aodh( + 'alarm', params="show --alarm-name calarm1") + 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('calarm1', alarm_show['name']) + + # GET BY NAME AND ID ERROR + self.assertRaises(exceptions.CommandFailed, + self.aodh, u'alarm', + params=(u"show %s --alarm-name calarm1" % + alarm_id)) + # LIST result = self.aodh('alarm', params="list") self.assertIn(alarm_id, @@ -323,7 +369,7 @@ class AodhClientGnocchiRulesTest(base.ClientTestBase): result = self.aodh(u'alarm', params=(u"create " "--type gnocchi_resources_threshold " - "--name alarm1 --metric cpu_util " + "--name alarm_gn1 --metric cpu_util " "--threshold 80 " "--resource-id %s --resource-type instance " "--aggregation-method last " @@ -331,7 +377,7 @@ class AodhClientGnocchiRulesTest(base.ClientTestBase): % (RESOURCE_ID, PROJECT_ID))) alarm = self.details_multiple(result)[0] ALARM_ID = alarm['alarm_id'] - self.assertEqual('alarm1', alarm['name']) + self.assertEqual('alarm_gn1', alarm['name']) self.assertEqual('cpu_util', alarm['metric']) self.assertEqual('80.0', alarm['threshold']) self.assertEqual('last', alarm['aggregation_method']) @@ -343,7 +389,7 @@ class AodhClientGnocchiRulesTest(base.ClientTestBase): result = self.aodh(u'alarm', params=(u"create " "--type gnocchi_resources_threshold " - "--name alarm1 --metric cpu_util " + "--name alarm_gn1 --metric cpu_util " "--threshold 80 " "--resource-id %s --resource-type instance " "--aggregation-method last " @@ -351,7 +397,8 @@ class AodhClientGnocchiRulesTest(base.ClientTestBase): % (RESOURCE_ID, PROJECT_ID)), fail_ok=True, merge_stderr=True) self.assertFirstLineStartsWith( - result.split('\n'), "Alarm with name='alarm1' exists (HTTP 409)") + result.split('\n'), + "Alarm with name='alarm_gn1' exists (HTTP 409)") # CREATE FAIL MISSING PARAM self.assertRaises(exceptions.CommandFailed, @@ -379,13 +426,31 @@ class AodhClientGnocchiRulesTest(base.ClientTestBase): 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('alarm1', alarm_show['name']) + self.assertEqual('alarm_gn1', alarm_show['name']) self.assertEqual('cpu_util', alarm_show['metric']) self.assertEqual('90.0', alarm_show['threshold']) self.assertEqual('critical', alarm_show['severity']) self.assertEqual('last', alarm_show['aggregation_method']) self.assertEqual('instance', alarm_show['resource_type']) + # GET BY NAME + result = self.aodh( + 'alarm', params="show --alarm-name alarm_gn1") + self.assertEqual(ALARM_ID, alarm_show["alarm_id"]) + self.assertEqual(PROJECT_ID, alarm_show["project_id"]) + self.assertEqual('alarm_gn1', alarm_show['name']) + self.assertEqual('cpu_util', alarm_show['metric']) + self.assertEqual('90.0', alarm_show['threshold']) + self.assertEqual('critical', alarm_show['severity']) + self.assertEqual('last', alarm_show['aggregation_method']) + self.assertEqual('instance', alarm_show['resource_type']) + + # GET BY NAME AND ID ERROR + self.assertRaises(exceptions.CommandFailed, + self.aodh, u'alarm', + params=(u"show %s --alarm-name alarm_gn1" % + ALARM_ID)) + # LIST result = self.aodh('alarm', params="list") self.assertIn(ALARM_ID, @@ -395,14 +460,14 @@ class AodhClientGnocchiRulesTest(base.ClientTestBase): 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('alarm1', alarm_list['name']) + self.assertEqual('alarm_gn1', alarm_list['name']) # 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('alarm1', alarm_list['name']) + self.assertEqual('alarm_gn1', alarm_list['name']) # DELETE result = self.aodh('alarm', params="delete %s" % ALARM_ID) diff --git a/aodhclient/v2/alarm_cli.py b/aodhclient/v2/alarm_cli.py index 4f7c9e9..6c0b209 100644 --- a/aodhclient/v2/alarm_cli.py +++ b/aodhclient/v2/alarm_cli.py @@ -19,6 +19,8 @@ from cliff import show from oslo_serialization import jsonutils from oslo_utils import strutils +from aodhclient import exceptions +from aodhclient.i18n import _ from aodhclient import utils ALARM_TYPES = ['threshold', 'event', 'composite', @@ -65,16 +67,56 @@ def _format_alarm(alarm): return alarm +def _find_alarm_by_name(client, name, return_id=False): + # then try to get entity as name + query = jsonutils.dumps({"=": {"name": name}}) + alarms = client.list(query) + if len(alarms) > 1: + msg = (_("Multiple alarms matches found for '%s', " + "use an ID to be more specific.") % name) + raise exceptions.NoUniqueMatch(msg) + elif not alarms: + msg = (_("Alarm %s not found") % name) + raise exceptions.NotFound(404, msg) + else: + if return_id: + return alarms[0]['alarm_id'] + return alarms[0] + + +def _check_name_and_id(parsed_args, action): + if parsed_args.id and parsed_args.alarm_name: + raise exceptions.CommandError( + "You should provide only one of " + "alarm ID and alarm name(--alarm-name) " + "to %s an alarm." % action) + if not parsed_args.id and not parsed_args.alarm_name: + msg = (_("You need to specify one of " + "alarm ID and alarm name(--alarm-name) " + "to %s an alarm.") % action) + raise exceptions.CommandError(msg) + + class CliAlarmShow(show.ShowOne): """Show an alarm""" def get_parser(self, prog_name): parser = super(CliAlarmShow, self).get_parser(prog_name) - parser.add_argument("alarm_id", help="ID of an alarm") + parser.add_argument("id", nargs='?', + metavar='', + help="ID of an alarm") + parser.add_argument("--alarm-name", dest='alarm_name', + metavar='', + help="name of an alarm") return parser def take_action(self, parsed_args): - alarm = self.app.client.alarm.get(alarm_id=parsed_args.alarm_id) + _check_name_and_id(parsed_args, 'query') + if parsed_args.id: + alarm = self.app.client.alarm.get(alarm_id=parsed_args.id) + else: + alarm = _find_alarm_by_name(self.app.client.alarm, + parsed_args.alarm_name) return self.dict2columns(_format_alarm(alarm)) @@ -323,13 +365,26 @@ class CliAlarmUpdate(CliAlarmCreate): def get_parser(self, prog_name): parser = super(CliAlarmUpdate, self).get_parser(prog_name) - parser.add_argument("alarm_id", help="ID of the alarm") + parser.add_argument("id", nargs='?', + metavar='', + help="ID of an alarm") + parser.add_argument("--alarm-name", dest='alarm_name', + metavar='', + help="name of an alarm") return parser def take_action(self, parsed_args): + _check_name_and_id(parsed_args, 'update') attributes = self._alarm_from_args(parsed_args) - updated_alarm = self.app.client.alarm.update( - alarm_id=parsed_args.alarm_id, alarm_update=attributes) + if parsed_args.id: + updated_alarm = self.app.client.alarm.update( + alarm_id=parsed_args.id, alarm_update=attributes) + else: + alarm_id = _find_alarm_by_name(self.app.client.alarm, + parsed_args.alarm_name, + return_id=True) + updated_alarm = self.app.client.alarm.update( + alarm_id=alarm_id, alarm_update=attributes) return self.dict2columns(_format_alarm(updated_alarm)) @@ -338,8 +393,20 @@ class CliAlarmDelete(command.Command): def get_parser(self, prog_name): parser = super(CliAlarmDelete, self).get_parser(prog_name) - parser.add_argument("alarm_id", help="ID of the alarm") + parser.add_argument("id", nargs='?', + metavar='', + help="ID of an alarm") + parser.add_argument("--alarm-name", dest='alarm_name', + metavar='', + help="name of an alarm") return parser def take_action(self, parsed_args): - self.app.client.alarm.delete(parsed_args.alarm_id) + _check_name_and_id(parsed_args, 'delete') + if parsed_args.id: + self.app.client.alarm.delete(parsed_args.id) + else: + alarm_id = _find_alarm_by_name(self.app.client.alarm, + parsed_args.alarm_name, + return_id=True) + self.app.client.alarm.delete(alarm_id)