Skip to content

Commit d16e00a

Browse files
committed
Allow to retrieve objects by name
Fixes bug 979527 xxx-show commands now can accept either an id or a name of the resource to retrieve, similarly to the "nova get" command. This has been preferred to using mutually exclusive keyword argument, in order to avoid confusion with other CLI tools. NOTE: the current patch allow search by name only for networks. The restriction will be lifted once name attributes for port and subnets are added. Change-Id: Id186139a01c9f2cfc36ca3405b4024bd7780622e
1 parent d70620c commit d16e00a

9 files changed

Lines changed: 134 additions & 28 deletions

File tree

quantumclient/common/exceptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class QuantumClientException(QuantumException):
5151

5252
def __init__(self, **kwargs):
5353
message = kwargs.get('message')
54+
self.status_code = kwargs.get('status_code', 0)
5455
if message:
5556
self.message = message
5657
super(QuantumClientException, self).__init__(**kwargs)

quantumclient/quantum/v2_0/__init__.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import argparse
1919
import logging
20+
import re
2021

2122
from cliff import lister
2223
from cliff import show
@@ -326,7 +327,10 @@ class ShowCommand(QuantumCommand, show.ShowOne):
326327
"""Show information of a given resource
327328
328329
"""
329-
330+
HEX_ELEM = '[0-9A-Fa-f]'
331+
UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}',
332+
HEX_ELEM + '{4}', HEX_ELEM + '{4}',
333+
HEX_ELEM + '{12}'])
330334
api = 'network'
331335
resource = None
332336
log = None
@@ -336,22 +340,70 @@ def get_parser(self, prog_name):
336340
add_show_list_common_argument(parser)
337341
parser.add_argument(
338342
'id', metavar='%s_id' % self.resource,
339-
help='ID of %s to look up' % self.resource)
340-
343+
help='ID or name of %s to look up' % self.resource)
341344
return parser
342345

343346
def get_data(self, parsed_args):
344347
self.log.debug('get_data(%s)' % parsed_args)
345348
quantum_client = self.get_client()
346349
quantum_client.format = parsed_args.request_format
350+
347351
params = {}
348352
if parsed_args.show_details:
349353
params = {'verbose': 'True'}
350354
if parsed_args.fields:
351355
params = {'fields': parsed_args.fields}
352-
obj_showor = getattr(quantum_client,
353-
"show_%s" % self.resource)
354-
data = obj_showor(parsed_args.id, **params)
356+
357+
data = None
358+
# Error message to be used in case both search by id and name are
359+
# unsuccessful (if list by name fails it does not return an error)
360+
not_found_message = "Unable to find resource:%s" % parsed_args.id
361+
362+
# perform search by id only if we are passing a valid UUID
363+
match = re.match(self.UUID_PATTERN, parsed_args.id)
364+
if match:
365+
try:
366+
obj_shower = getattr(quantum_client,
367+
"show_%s" % self.resource)
368+
data = obj_shower(parsed_args.id, **params)
369+
except exceptions.QuantumClientException as ex:
370+
logging.debug("Show operation failed with code:%s",
371+
ex.status_code)
372+
not_found_message = ex.message
373+
if ex.status_code != 404:
374+
logging.exception("Unable to perform show operation")
375+
raise
376+
377+
# If data is empty, then we got a 404. Try to interpret Id as a name
378+
if not data:
379+
logging.debug("Trying to interpret %s as a %s name",
380+
parsed_args.id,
381+
self.resource)
382+
# build search_opts for the name
383+
search_opts = parse_args_to_dict(["--name=%s" % parsed_args.id])
384+
search_opts.update(params)
385+
obj_lister = getattr(quantum_client,
386+
"list_%ss" % self.resource)
387+
data = obj_lister(**search_opts)
388+
info = []
389+
collection = self.resource + "s"
390+
if collection in data:
391+
info = data[collection]
392+
if len(info) > 1:
393+
logging.info("Multiple occurrences found for: %s",
394+
parsed_args.id)
395+
_columns = ['id']
396+
# put all ids in a single string as formatter for show
397+
# command will print on record only
398+
id_string = "\n".join(utils.get_item_properties(
399+
s, _columns)[0] for s in info)
400+
return (_columns, (id_string, ), )
401+
elif len(info) == 0:
402+
#Nothing was found
403+
raise exceptions.QuantumClientException(
404+
message=not_found_message)
405+
else:
406+
data = {self.resource: info[0]}
355407
if self.resource in data:
356408
for k, v in data[self.resource].iteritems():
357409
if isinstance(v, list):

quantumclient/quantum/v2_0/port.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def add_known_arguments(self, parser):
7272
'can be repeated')
7373
parser.add_argument(
7474
'network_id',
75-
help='Network id of this port belongs to')
75+
help='Network id this port belongs to')
7676

7777
def args2body(self, parsed_args):
7878
body = {'port': {'admin_state_up': parsed_args.admin_state_down,

quantumclient/quantum/v2_0/subnet.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def add_known_arguments(self, parser):
6969
'can be repeated')
7070
parser.add_argument(
7171
'network_id',
72-
help='Network id of this subnet belongs to')
72+
help='Network id this subnet belongs to')
7373
parser.add_argument(
7474
'cidr', metavar='cidr',
7575
help='cidr of subnet to create')

quantumclient/tests/unit/test_cli20.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ def __repr__(self):
107107

108108
class CLITestV20Base(unittest.TestCase):
109109

110+
test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
111+
110112
def _url(self, path, query=None):
111113
_url_str = self.endurl + "/v" + API_VERSION + path + "." + FORMAT
112114
return query and _url_str + "?" + query or _url_str
@@ -242,16 +244,11 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]):
242244
self.mox.StubOutWithMock(cmd, "get_client")
243245
self.mox.StubOutWithMock(self.client.httpclient, "request")
244246
cmd.get_client().MultipleTimes().AndReturn(self.client)
245-
query = None
246-
for field in fields:
247-
if query:
248-
query += "&fields=" + field
249-
else:
250-
query = "fields=" + field
251-
resnetworks = {resource:
252-
{'id': myid,
247+
query = "&".join(["fields=%s" % field for field in fields])
248+
expected_res = {resource:
249+
{'id': myid,
253250
'name': 'myname', }, }
254-
resstr = self.client.serialize(resnetworks)
251+
resstr = self.client.serialize(expected_res)
255252
path = getattr(self.client, resource + "_path")
256253
self.client.httpclient.request(
257254
self._url(path % myid, query), 'GET',
@@ -269,6 +266,33 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]):
269266
self.assertTrue(myid in _str)
270267
self.assertTrue('myname' in _str)
271268

269+
def _test_show_resource_by_name(self, resource, cmd, name,
270+
args, fields=[]):
271+
self.mox.StubOutWithMock(cmd, "get_client")
272+
self.mox.StubOutWithMock(self.client.httpclient, "request")
273+
cmd.get_client().MultipleTimes().AndReturn(self.client)
274+
query = "&".join(["fields=%s" % field for field in fields])
275+
expected_res = {"%ss" % resource:
276+
[{'id': 'some_id',
277+
'name': name, }], }
278+
resstr = self.client.serialize(expected_res)
279+
list_path = getattr(self.client, resource + "s_path")
280+
self.client.httpclient.request(
281+
self._url(list_path, "%s&name=%s" % (query, name)), 'GET',
282+
body=None,
283+
headers=ContainsKeyValue('X-Auth-Token',
284+
TOKEN)).AndReturn((MyResp(200), resstr))
285+
self.mox.ReplayAll()
286+
cmd_parser = cmd.get_parser("show_" + resource)
287+
288+
parsed_args = cmd_parser.parse_args(args)
289+
cmd.run(parsed_args)
290+
self.mox.VerifyAll()
291+
self.mox.UnsetStubs()
292+
_str = self.fake_stdout.make_string()
293+
self.assertTrue(name in _str)
294+
self.assertTrue('some_id' in _str)
295+
272296
def _test_delete_resource(self, resource, cmd, myid, args):
273297
self.mox.StubOutWithMock(cmd, "get_client")
274298
self.mox.StubOutWithMock(self.client.httpclient, "request")

quantumclient/tests/unit/test_cli20_network.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,18 @@ def test_show_network(self):
125125
"""Show net: --fields id --fields name myid."""
126126
resource = 'network'
127127
cmd = ShowNetwork(MyApp(sys.stdout), None)
128-
myid = 'myid'
129-
args = ['--fields', 'id', '--fields', 'name', myid]
130-
self._test_show_resource(resource, cmd, myid, args, ['id', 'name'])
128+
args = ['--fields', 'id', '--fields', 'name', self.test_id]
129+
self._test_show_resource(resource, cmd, self.test_id, args,
130+
['id', 'name'])
131+
132+
def test_show_network_by_name(self):
133+
"""Show net: --fields id --fields name myname."""
134+
resource = 'network'
135+
cmd = ShowNetwork(MyApp(sys.stdout), None)
136+
myname = 'myname'
137+
args = ['--fields', 'id', '--fields', 'name', myname]
138+
self._test_show_resource_by_name(resource, cmd, myname,
139+
args, ['id', 'name'])
131140

132141
def test_delete_network(self):
133142
"""Delete net: myid."""

quantumclient/tests/unit/test_cli20_port.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,18 @@ def test_show_port(self):
124124
"""Show port: --fields id --fields name myid."""
125125
resource = 'port'
126126
cmd = ShowPort(MyApp(sys.stdout), None)
127-
myid = 'myid'
128-
args = ['--fields', 'id', '--fields', 'name', myid]
129-
self._test_show_resource(resource, cmd, myid, args, ['id', 'name'])
127+
args = ['--fields', 'id', '--fields', 'name', self.test_id]
128+
self._test_show_resource(resource, cmd, self.test_id,
129+
args, ['id', 'name'])
130+
131+
def test_show_port_by_name(self):
132+
"""Show port: --fields id --fields name myname."""
133+
resource = 'port'
134+
cmd = ShowPort(MyApp(sys.stdout), None)
135+
myname = 'myname'
136+
args = ['--fields', 'id', '--fields', 'name', myname]
137+
self._test_show_resource_by_name(resource, cmd, myname,
138+
args, ['id', 'name'])
130139

131140
def test_delete_port(self):
132141
"""Delete port: myid."""

quantumclient/tests/unit/test_cli20_subnet.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,18 @@ def test_show_subnet(self):
157157
"""Show subnet: --fields id --fields name myid."""
158158
resource = 'subnet'
159159
cmd = ShowSubnet(MyApp(sys.stdout), None)
160-
myid = 'myid'
161-
args = ['--fields', 'id', '--fields', 'name', myid]
162-
self._test_show_resource(resource, cmd, myid, args, ['id', 'name'])
160+
args = ['--fields', 'id', '--fields', 'name', self.test_id]
161+
self._test_show_resource(resource, cmd, self.test_id,
162+
args, ['id', 'name'])
163+
164+
def test_show_subnet_by_name(self):
165+
"""Show subnet: --fields id --fields name myname."""
166+
resource = 'subnet'
167+
cmd = ShowSubnet(MyApp(sys.stdout), None)
168+
myname = 'myname'
169+
args = ['--fields', 'id', '--fields', 'name', myname]
170+
self._test_show_resource_by_name(resource, cmd, myname,
171+
args, ['id', 'name'])
163172

164173
def test_delete_subnet(self):
165174
"""Delete subnet: subnetid."""

quantumclient/v2_0/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,13 @@ def exception_handler_v20(status_code, error_content):
7878
if isinstance(error_content, dict):
7979
message = error_content.get('message', None)
8080
if message:
81-
raise exceptions.QuantumClientException(message=message)
81+
raise exceptions.QuantumClientException(status_code=status_code,
82+
message=message)
8283

8384
# If we end up here the exception was not a quantum error
8485
msg = "%s-%s" % (status_code, error_content)
85-
raise exceptions.QuantumClientException(message=msg)
86+
raise exceptions.QuantumClientException(status_code=status_code,
87+
message=msg)
8688

8789

8890
class APIParamsCall(object):

0 commit comments

Comments
 (0)