Adds client commands for listing a VNF resources
Support now for being able list sub resources of a vnf. REST path is /vnf/<vnf_id>/resources/. Resources will contain 'name', 'id', and 'type'. APIImpact Partial-Bug: 1602112 Change-Id: Ib9f0163c0c86df2a4d17630a5e6f7ca2d2fb22de Signed-off-by: Tim Rozet <trozet@redhat.com>
This commit is contained in:
parent
7f829587b0
commit
144408331e
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Adds new CLI command 'vnf-resource-list' to view VNF
|
||||||
|
resources, such as VDU, CP, etc.
|
@ -117,6 +117,7 @@ COMMAND_V1 = {
|
|||||||
'vnf-list': vnf.ListVNF,
|
'vnf-list': vnf.ListVNF,
|
||||||
'vnf-show': vnf.ShowVNF,
|
'vnf-show': vnf.ShowVNF,
|
||||||
'vnf-scale': vnf.ScaleVNF,
|
'vnf-scale': vnf.ScaleVNF,
|
||||||
|
'vnf-resource-list': vnf.ListVNFResources,
|
||||||
# 'vnf-config-create'
|
# 'vnf-config-create'
|
||||||
# 'vnf-config-push'
|
# 'vnf-config-push'
|
||||||
|
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from tackerclient.i18n import _
|
||||||
from tackerclient.tacker import v1_0 as tackerV10
|
from tackerclient.tacker import v1_0 as tackerV10
|
||||||
|
|
||||||
|
|
||||||
_VNF = 'vnf'
|
_VNF = 'vnf'
|
||||||
|
_RESOURCE = 'resource'
|
||||||
|
|
||||||
|
|
||||||
class ListVNF(tackerV10.ListCommand):
|
class ListVNF(tackerV10.ListCommand):
|
||||||
@ -149,6 +151,73 @@ class DeleteVNF(tackerV10.DeleteCommand):
|
|||||||
resource = _VNF
|
resource = _VNF
|
||||||
|
|
||||||
|
|
||||||
|
class ListVNFResources(tackerV10.ListCommand):
|
||||||
|
"""List resources of a VNF like VDU, CP, etc."""
|
||||||
|
|
||||||
|
list_columns = ['name', 'id', 'type']
|
||||||
|
allow_names = True
|
||||||
|
resource = _VNF
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
if self.resource:
|
||||||
|
return self.resource.upper()
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(ListVNFResources, self).get_parser(prog_name)
|
||||||
|
if self.allow_names:
|
||||||
|
help_str = _('ID or name of %s to look up')
|
||||||
|
else:
|
||||||
|
help_str = _('ID of %s to look up')
|
||||||
|
parser.add_argument(
|
||||||
|
'id', metavar=self.get_id(),
|
||||||
|
help=help_str % self.resource)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def get_data(self, parsed_args):
|
||||||
|
self.log.debug('get_data(%s)', parsed_args)
|
||||||
|
tacker_client = self.get_client()
|
||||||
|
tacker_client.format = parsed_args.request_format
|
||||||
|
if self.allow_names:
|
||||||
|
_id = tackerV10.find_resourceid_by_name_or_id(tacker_client,
|
||||||
|
self.resource,
|
||||||
|
parsed_args.id)
|
||||||
|
else:
|
||||||
|
_id = parsed_args.id
|
||||||
|
|
||||||
|
data = self.retrieve_list_by_id(_id, parsed_args)
|
||||||
|
self.extend_list(data, parsed_args)
|
||||||
|
return self.setup_columns(data, parsed_args)
|
||||||
|
|
||||||
|
def retrieve_list_by_id(self, id, parsed_args):
|
||||||
|
"""Retrieve a list of sub resources from Tacker server"""
|
||||||
|
tacker_client = self.get_client()
|
||||||
|
tacker_client.format = parsed_args.request_format
|
||||||
|
_extra_values = tackerV10.parse_args_to_dict(self.values_specs)
|
||||||
|
tackerV10._merge_args(self, parsed_args, _extra_values,
|
||||||
|
self.values_specs)
|
||||||
|
search_opts = self.args2search_opts(parsed_args)
|
||||||
|
search_opts.update(_extra_values)
|
||||||
|
if self.pagination_support:
|
||||||
|
page_size = parsed_args.page_size
|
||||||
|
if page_size:
|
||||||
|
search_opts.update({'limit': page_size})
|
||||||
|
if self.sorting_support:
|
||||||
|
keys = parsed_args.sort_key
|
||||||
|
if keys:
|
||||||
|
search_opts.update({'sort_key': keys})
|
||||||
|
dirs = parsed_args.sort_dir
|
||||||
|
len_diff = len(keys) - len(dirs)
|
||||||
|
if len_diff > 0:
|
||||||
|
dirs += ['asc'] * len_diff
|
||||||
|
elif len_diff < 0:
|
||||||
|
dirs = dirs[:len(keys)]
|
||||||
|
if dirs:
|
||||||
|
search_opts.update({'sort_dir': dirs})
|
||||||
|
obj_lister = getattr(tacker_client, "list_vnf_resources")
|
||||||
|
data = obj_lister(id, **search_opts)
|
||||||
|
return data.get('resources', [])
|
||||||
|
|
||||||
|
|
||||||
class ScaleVNF(tackerV10.TackerCommand):
|
class ScaleVNF(tackerV10.TackerCommand):
|
||||||
"""Scale a VNF."""
|
"""Scale a VNF."""
|
||||||
|
|
||||||
|
@ -369,6 +369,139 @@ class CLITestV10Base(testtools.TestCase):
|
|||||||
self.assertIn('myid1', _str)
|
self.assertIn('myid1', _str)
|
||||||
return _str
|
return _str
|
||||||
|
|
||||||
|
def _test_list_sub_resources(self, resources, api_resource, cmd, myid,
|
||||||
|
detail=False,
|
||||||
|
tags=[], fields_1=[], fields_2=[],
|
||||||
|
page_size=None, sort_key=[], sort_dir=[],
|
||||||
|
response_contents=None, base_args=None,
|
||||||
|
path=None):
|
||||||
|
self.mox.StubOutWithMock(cmd, "get_client")
|
||||||
|
self.mox.StubOutWithMock(self.client.httpclient, "request")
|
||||||
|
cmd.get_client().MultipleTimes().AndReturn(self.client)
|
||||||
|
if response_contents is None:
|
||||||
|
contents = [{self.id_field: 'myid1', },
|
||||||
|
{self.id_field: 'myid2', }, ]
|
||||||
|
else:
|
||||||
|
contents = response_contents
|
||||||
|
reses = {api_resource: contents}
|
||||||
|
self.client.format = self.format
|
||||||
|
resstr = self.client.serialize(reses)
|
||||||
|
# url method body
|
||||||
|
query = ""
|
||||||
|
args = base_args if base_args is not None else []
|
||||||
|
if detail:
|
||||||
|
args.append('-D')
|
||||||
|
args.extend(['--request-format', self.format])
|
||||||
|
if fields_1:
|
||||||
|
for field in fields_1:
|
||||||
|
args.append('--fields')
|
||||||
|
args.append(field)
|
||||||
|
|
||||||
|
if tags:
|
||||||
|
args.append('--')
|
||||||
|
args.append("--tag")
|
||||||
|
for tag in tags:
|
||||||
|
args.append(tag)
|
||||||
|
if isinstance(tag, six.string_types):
|
||||||
|
tag = urllib.quote(tag.encode('utf-8'))
|
||||||
|
if query:
|
||||||
|
query += "&tag=" + tag
|
||||||
|
else:
|
||||||
|
query = "tag=" + tag
|
||||||
|
if (not tags) and fields_2:
|
||||||
|
args.append('--')
|
||||||
|
if fields_2:
|
||||||
|
args.append("--fields")
|
||||||
|
for field in fields_2:
|
||||||
|
args.append(field)
|
||||||
|
if detail:
|
||||||
|
query = query and query + '&verbose=True' or 'verbose=True'
|
||||||
|
fields_1.extend(fields_2)
|
||||||
|
for field in fields_1:
|
||||||
|
if query:
|
||||||
|
query += "&fields=" + field
|
||||||
|
else:
|
||||||
|
query = "fields=" + field
|
||||||
|
if page_size:
|
||||||
|
args.append("--page-size")
|
||||||
|
args.append(str(page_size))
|
||||||
|
if query:
|
||||||
|
query += "&limit=%s" % page_size
|
||||||
|
else:
|
||||||
|
query = "limit=%s" % page_size
|
||||||
|
if sort_key:
|
||||||
|
for key in sort_key:
|
||||||
|
args.append('--sort-key')
|
||||||
|
args.append(key)
|
||||||
|
if query:
|
||||||
|
query += '&'
|
||||||
|
query += 'sort_key=%s' % key
|
||||||
|
if sort_dir:
|
||||||
|
len_diff = len(sort_key) - len(sort_dir)
|
||||||
|
if len_diff > 0:
|
||||||
|
sort_dir += ['asc'] * len_diff
|
||||||
|
elif len_diff < 0:
|
||||||
|
sort_dir = sort_dir[:len(sort_key)]
|
||||||
|
for dir in sort_dir:
|
||||||
|
args.append('--sort-dir')
|
||||||
|
args.append(dir)
|
||||||
|
if query:
|
||||||
|
query += '&'
|
||||||
|
query += 'sort_dir=%s' % dir
|
||||||
|
if path is None:
|
||||||
|
path = getattr(self.client, resources + "_path")
|
||||||
|
self.client.httpclient.request(
|
||||||
|
MyUrlComparator(end_url(path % myid, query, format=self.format),
|
||||||
|
self.client),
|
||||||
|
'GET',
|
||||||
|
body=None,
|
||||||
|
headers=mox.ContainsKeyValue(
|
||||||
|
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr))
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
cmd_parser = cmd.get_parser("list_" + resources)
|
||||||
|
shell.run_command(cmd, cmd_parser, args)
|
||||||
|
self.mox.VerifyAll()
|
||||||
|
self.mox.UnsetStubs()
|
||||||
|
_str = self.fake_stdout.make_string()
|
||||||
|
if response_contents is None:
|
||||||
|
self.assertIn('myid1', _str)
|
||||||
|
return _str
|
||||||
|
|
||||||
|
def _test_list_sub_resources_with_pagination(self, resources, api_resource,
|
||||||
|
cmd, myid):
|
||||||
|
self.mox.StubOutWithMock(cmd, "get_client")
|
||||||
|
self.mox.StubOutWithMock(self.client.httpclient, "request")
|
||||||
|
cmd.get_client().MultipleTimes().AndReturn(self.client)
|
||||||
|
path = getattr(self.client, resources + "_path")
|
||||||
|
fake_query = "marker=myid2&limit=2"
|
||||||
|
reses1 = {api_resource: [{'id': 'myid1', },
|
||||||
|
{'id': 'myid2', }],
|
||||||
|
'%s_links' % api_resource: [
|
||||||
|
{'href': end_url(path % myid, fake_query),
|
||||||
|
'rel': 'next'}]
|
||||||
|
}
|
||||||
|
reses2 = {api_resource: [{'id': 'myid3', },
|
||||||
|
{'id': 'myid4', }]}
|
||||||
|
self.client.format = self.format
|
||||||
|
resstr1 = self.client.serialize(reses1)
|
||||||
|
resstr2 = self.client.serialize(reses2)
|
||||||
|
self.client.httpclient.request(
|
||||||
|
end_url(path % myid, "", format=self.format), 'GET',
|
||||||
|
body=None,
|
||||||
|
headers=mox.ContainsKeyValue(
|
||||||
|
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr1))
|
||||||
|
self.client.httpclient.request(
|
||||||
|
end_url(path % myid, fake_query, format=self.format), 'GET',
|
||||||
|
body=None,
|
||||||
|
headers=mox.ContainsKeyValue(
|
||||||
|
'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr2))
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
cmd_parser = cmd.get_parser("list_" + resources)
|
||||||
|
args = [myid, '--request-format', self.format]
|
||||||
|
shell.run_command(cmd, cmd_parser, args)
|
||||||
|
self.mox.VerifyAll()
|
||||||
|
self.mox.UnsetStubs()
|
||||||
|
|
||||||
def _test_list_resources_with_pagination(self, resources, cmd):
|
def _test_list_resources_with_pagination(self, resources, cmd):
|
||||||
self.mox.StubOutWithMock(cmd, "get_client")
|
self.mox.StubOutWithMock(cmd, "get_client")
|
||||||
self.mox.StubOutWithMock(self.client.httpclient, "request")
|
self.mox.StubOutWithMock(self.client.httpclient, "request")
|
||||||
|
@ -32,9 +32,11 @@ ENDURL = 'localurl'
|
|||||||
class CLITestV10VmVNFJSON(test_cli10.CLITestV10Base):
|
class CLITestV10VmVNFJSON(test_cli10.CLITestV10Base):
|
||||||
_RESOURCE = 'vnf'
|
_RESOURCE = 'vnf'
|
||||||
_RESOURCES = 'vnfs'
|
_RESOURCES = 'vnfs'
|
||||||
|
_VNF_RESOURCES = 'vnf_resources'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
plurals = {'vnfs': 'vnf'}
|
plurals = {'vnfs': 'vnf',
|
||||||
|
'resources': 'resource'}
|
||||||
super(CLITestV10VmVNFJSON, self).setUp(plurals=plurals)
|
super(CLITestV10VmVNFJSON, self).setUp(plurals=plurals)
|
||||||
|
|
||||||
def _test_create_resource(self, resource, cmd,
|
def _test_create_resource(self, resource, cmd,
|
||||||
@ -192,3 +194,22 @@ class CLITestV10VmVNFJSON(test_cli10.CLITestV10Base):
|
|||||||
my_id = 'my-id'
|
my_id = 'my-id'
|
||||||
args = [my_id]
|
args = [my_id]
|
||||||
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)
|
self._test_delete_resource(self._RESOURCE, cmd, my_id, args)
|
||||||
|
|
||||||
|
def test_list_vnf_resources(self):
|
||||||
|
cmd = vnf.ListVNFResources(test_cli10.MyApp(sys.stdout), None)
|
||||||
|
base_args = [self.test_id]
|
||||||
|
response = [{'name': 'CP11', 'id': 'id1', 'type': 'NeutronPort'},
|
||||||
|
{'name': 'CP12', 'id': 'id2', 'type': 'NeutronPort'}]
|
||||||
|
val = self._test_list_sub_resources(self._VNF_RESOURCES, 'resources',
|
||||||
|
cmd, self.test_id,
|
||||||
|
response_contents=response,
|
||||||
|
detail=True, base_args=base_args)
|
||||||
|
self.assertIn('id1', val)
|
||||||
|
self.assertIn('NeutronPort', val)
|
||||||
|
self.assertIn('CP11', val)
|
||||||
|
|
||||||
|
def test_list_vnf_resources_pagination(self):
|
||||||
|
cmd = vnf.ListVNFResources(test_cli10.MyApp(sys.stdout), None)
|
||||||
|
self._test_list_sub_resources_with_pagination(self._VNF_RESOURCES,
|
||||||
|
'resources', cmd,
|
||||||
|
self.test_id)
|
||||||
|
@ -339,6 +339,7 @@ class Client(ClientBase):
|
|||||||
vnfs_path = '/vnfs'
|
vnfs_path = '/vnfs'
|
||||||
vnf_path = '/vnfs/%s'
|
vnf_path = '/vnfs/%s'
|
||||||
vnf_scale_path = '/vnfs/%s/actions'
|
vnf_scale_path = '/vnfs/%s/actions'
|
||||||
|
vnf_resources_path = '/vnfs/%s/resources'
|
||||||
|
|
||||||
vims_path = '/vims'
|
vims_path = '/vims'
|
||||||
vim_path = '/vims/%s'
|
vim_path = '/vims/%s'
|
||||||
@ -425,6 +426,11 @@ class Client(ClientBase):
|
|||||||
def update_vnf(self, vnf, body=None):
|
def update_vnf(self, vnf, body=None):
|
||||||
return self.put(self.vnf_path % vnf, body=body)
|
return self.put(self.vnf_path % vnf, body=body)
|
||||||
|
|
||||||
|
@APIParamsCall
|
||||||
|
def list_vnf_resources(self, vnf, retrieve_all=True, **_params):
|
||||||
|
return self.list('resources', self.vnf_resources_path % vnf,
|
||||||
|
retrieve_all, **_params)
|
||||||
|
|
||||||
@APIParamsCall
|
@APIParamsCall
|
||||||
def scale_vnf(self, vnf, body=None):
|
def scale_vnf(self, vnf, body=None):
|
||||||
return self.post(self.vnf_scale_path % vnf, body=body)
|
return self.post(self.vnf_scale_path % vnf, body=body)
|
||||||
|
Loading…
Reference in New Issue
Block a user