Support CLI for Getting List of VNF LCM Operation Occurrences
Add ``openstack vnflcm op list`` to python-tackerclient. This command can execute getting the list of VNF LCM Operation Occurrences. User can specify filters for more specific results. Note: - Filtering for the following attributes: operationParams, error, resourceChanges and changedInfo is only limited to the parent attribute. Currently, child attributes/nested attributes are not searchable. Implements: blueprint support-fundamental-lcm Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-fundamental-vnf-lcm-based-on-ETSI-NFV.html Change-Id: Ie0b3399946d2a705011269025102d9380102ca92
This commit is contained in:
@@ -98,3 +98,4 @@ openstack.tackerclient.v1 =
|
||||
vnflcm_op_rollback = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RollbackVnfLcmOp
|
||||
vnflcm_op_fail = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:FailVnfLcmOp
|
||||
vnflcm_op_retry = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RetryVnfLcmOp
|
||||
vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp
|
||||
|
@@ -18,9 +18,11 @@ from tackerclient.osc import utils as tacker_osc_utils
|
||||
|
||||
_VNF_LCM_OP_OCC_ID = 'vnf_lcm_op_occ_id'
|
||||
|
||||
_MIXED_CASE_FIELDS = ('operationState', 'stateEnteredTime', 'startTime',
|
||||
'vnfInstanceId', 'isAutomaticInvocation',
|
||||
'isCancelPending')
|
||||
_MIXED_CASE_FIELDS = ['operationState', 'stateEnteredTime', 'startTime',
|
||||
'vnfInstanceId', 'grantId', 'isAutomaticInvocation',
|
||||
'isCancelPending', 'cancelMode', 'operationParams',
|
||||
'resourceChanges', 'changedInfo',
|
||||
'changedExtConnectivity']
|
||||
|
||||
_FORMATTERS = {'error': tacker_osc_utils.FormatComplexDataColumn,
|
||||
'_links': tacker_osc_utils.FormatComplexDataColumn}
|
||||
@@ -141,3 +143,105 @@ class RetryVnfLcmOp(command.Command):
|
||||
if not result:
|
||||
print((_('Retry request for LCM operation %(id)s has been'
|
||||
' accepted') % {'id': parsed_args.vnf_lcm_op_occ_id}))
|
||||
|
||||
|
||||
class ListVnfLcmOp(command.Lister):
|
||||
_description = _("List LCM Operation Occurrences")
|
||||
|
||||
def get_parser(self, program_name):
|
||||
"""Add arguments to parser.
|
||||
|
||||
Args:
|
||||
program_name ([type]): program name
|
||||
|
||||
Returns:
|
||||
parser([ArgumentParser]):
|
||||
"""
|
||||
parser = super(ListVnfLcmOp, self).get_parser(program_name)
|
||||
parser.add_argument(
|
||||
"--filter",
|
||||
metavar="<filter>",
|
||||
help=_("Attribute-based-filtering parameters"),
|
||||
)
|
||||
fields_exclusive_group = parser.add_mutually_exclusive_group(
|
||||
required=False)
|
||||
fields_exclusive_group.add_argument(
|
||||
"--fields",
|
||||
metavar="<fields>",
|
||||
help=_("Complex attributes to be included into the response"),
|
||||
)
|
||||
fields_exclusive_group.add_argument(
|
||||
"--exclude-fields",
|
||||
metavar="<exclude-fields>",
|
||||
help=_("Complex attributes to be excluded from the response"),
|
||||
)
|
||||
return parser
|
||||
|
||||
def get_attributes(self):
|
||||
"""Get attributes.
|
||||
|
||||
Returns:
|
||||
attributes([attributes]): a list of table entry definitions.
|
||||
Each entry should be a tuple consisting of
|
||||
(API attribute name, header name, listing mode).
|
||||
"""
|
||||
fields = [
|
||||
{
|
||||
"key": "id",
|
||||
"value": "ID"
|
||||
},
|
||||
{
|
||||
"key": "operationState",
|
||||
"value": "Operation State"
|
||||
},
|
||||
{
|
||||
"key": "vnfInstanceId",
|
||||
"value": "VNF Instance ID"
|
||||
},
|
||||
{
|
||||
"key": "operation",
|
||||
"value": "Operation"
|
||||
}
|
||||
]
|
||||
|
||||
attributes = []
|
||||
for field in fields:
|
||||
attributes.extend([(field['key'], field['value'],
|
||||
tacker_osc_utils.LIST_BOTH)])
|
||||
|
||||
return tuple(attributes)
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
"""Execute list_vnflcm_op_occs and output response.
|
||||
|
||||
Args:
|
||||
parsed_args ([Namespace]): arguments of CLI.
|
||||
"""
|
||||
params = {}
|
||||
exclude_fields = []
|
||||
extra_fields = []
|
||||
|
||||
if parsed_args.filter:
|
||||
params['filter'] = parsed_args.filter
|
||||
if parsed_args.fields:
|
||||
params['fields'] = parsed_args.fields
|
||||
fields = parsed_args.fields.split(',')
|
||||
for field in fields:
|
||||
extra_fields.append(field.split('/')[0])
|
||||
if parsed_args.exclude_fields:
|
||||
params['exclude-fields'] = parsed_args.exclude_fields
|
||||
fields = parsed_args.exclude_fields.split(',')
|
||||
exclude_fields.extend(fields)
|
||||
|
||||
client = self.app.client_manager.tackerclient
|
||||
vnflcm_op_occs = client.list_vnf_lcm_op_occs(**params)
|
||||
headers, columns = tacker_osc_utils.get_column_definitions(
|
||||
self.get_attributes(),
|
||||
long_listing=True)
|
||||
|
||||
dictionary_properties = (utils.get_dict_properties(
|
||||
s, columns, mixed_case_fields=_MIXED_CASE_FIELDS)
|
||||
for s in vnflcm_op_occs
|
||||
)
|
||||
|
||||
return (headers, dictionary_properties)
|
||||
|
@@ -18,19 +18,30 @@ from oslo_utils.fixture import uuidsentinel
|
||||
from unittest import mock
|
||||
|
||||
from tackerclient.common import exceptions
|
||||
from tackerclient.osc import utils as tacker_osc_utils
|
||||
from tackerclient.osc.v1.vnflcm import vnflcm_op_occs
|
||||
from tackerclient.tests.unit.osc import base
|
||||
from tackerclient.tests.unit.osc.v1.fixture_data import client
|
||||
from tackerclient.tests.unit.osc.v1 import vnflcm_op_occs_fakes
|
||||
|
||||
|
||||
def _get_columns_vnflcm_op_occs():
|
||||
columns = ['ID', 'Operation State', 'State Entered Time',
|
||||
'Start Time', 'VNF Instance ID', 'Operation',
|
||||
'Is Automatic Invocation', 'Is Cancel Pending',
|
||||
'Error', 'Links']
|
||||
def _get_columns_vnflcm_op_occs(action=''):
|
||||
|
||||
return columns
|
||||
if action == 'fail':
|
||||
return ['ID', 'Operation State', 'State Entered Time',
|
||||
'Start Time', 'VNF Instance ID', 'Operation',
|
||||
'Is Automatic Invocation', 'Is Cancel Pending',
|
||||
'Error', 'Links']
|
||||
elif action == 'list':
|
||||
return ['ID', 'Operation State', 'VNF Instance ID',
|
||||
'Operation']
|
||||
else:
|
||||
return ['ID', 'Operation State', 'State Entered Time',
|
||||
'Start Time', 'VNF Instance ID', 'Grant ID',
|
||||
'Operation', 'Is Automatic Invocation',
|
||||
'Operation Parameters', 'Is Cancel Pending',
|
||||
'Cancel Mode', 'Error', 'Resource Changes',
|
||||
'Changed Info', 'Changed External Connectivity', 'Links']
|
||||
|
||||
|
||||
class TestVnfLcm(base.FixturedTestCase):
|
||||
@@ -114,7 +125,8 @@ class TestFailVnfLcmOp(TestVnfLcm):
|
||||
def test_take_action(self):
|
||||
"""Test of take_action()"""
|
||||
|
||||
vnflcm_op_occ = vnflcm_op_occs_fakes.vnflcm_op_occ_response()
|
||||
vnflcm_op_occ = vnflcm_op_occs_fakes.vnflcm_op_occ_response(
|
||||
action='fail')
|
||||
|
||||
arg_list = [vnflcm_op_occ['id']]
|
||||
verify_list = [('vnf_lcm_op_occ_id', vnflcm_op_occ['id'])]
|
||||
@@ -132,7 +144,7 @@ class TestFailVnfLcmOp(TestVnfLcm):
|
||||
'POST', url, headers=self.header, json=vnflcm_op_occ)
|
||||
|
||||
columns, data = (self.fail_vnf_lcm.take_action(parsed_args))
|
||||
expected_columns = _get_columns_vnflcm_op_occs()
|
||||
expected_columns = _get_columns_vnflcm_op_occs(action='fail')
|
||||
|
||||
self.assertCountEqual(expected_columns, columns)
|
||||
|
||||
@@ -324,3 +336,95 @@ class TestRetryVnfLcmOp(TestVnfLcm):
|
||||
verify_list = [('vnf_lcm_op_occ_id', arg_list)]
|
||||
self.assertRaises(base.ParserException, self.check_parser,
|
||||
self.retry_vnf_lcm, arg_list, verify_list)
|
||||
|
||||
|
||||
class TestListVnfLcmOp(TestVnfLcm):
|
||||
|
||||
vnflcm_op_occs_obj = vnflcm_op_occs_fakes.create_vnflcm_op_occs(count=3)
|
||||
|
||||
def setUp(self):
|
||||
super(TestListVnfLcmOp, self).setUp()
|
||||
self.list_vnflcm_op_occ = vnflcm_op_occs.ListVnfLcmOp(
|
||||
self.app, self.app_args, cmd_name='vnflcm op list')
|
||||
|
||||
def test_take_action(self):
|
||||
parsed_args = self.check_parser(self.list_vnflcm_op_occ, [], [])
|
||||
self.requests_mock.register_uri(
|
||||
'GET', os.path.join(self.url,
|
||||
'vnflcm/v1/vnf_lcm_op_occs'),
|
||||
json=self.vnflcm_op_occs_obj, headers=self.header)
|
||||
|
||||
actual_columns, data = self.list_vnflcm_op_occ.take_action(parsed_args)
|
||||
|
||||
headers, columns = tacker_osc_utils.get_column_definitions(
|
||||
self.list_vnflcm_op_occ.get_attributes(), long_listing=True)
|
||||
|
||||
expected_data = []
|
||||
for vnflcm_op_occ_obj in self.vnflcm_op_occs_obj:
|
||||
expected_data.append(vnflcm_op_occs_fakes.get_vnflcm_op_occ_data(
|
||||
vnflcm_op_occ_obj, columns=columns))
|
||||
|
||||
self.assertItemsEqual(_get_columns_vnflcm_op_occs(action='list'),
|
||||
actual_columns)
|
||||
self.assertItemsEqual(expected_data, list(data))
|
||||
|
||||
def test_take_action_with_filter(self):
|
||||
parsed_args = self.check_parser(
|
||||
self.list_vnflcm_op_occ,
|
||||
["--filter", '(eq,operationState,STARTING)'],
|
||||
[('filter', '(eq,operationState,STARTING)')])
|
||||
self.requests_mock.register_uri(
|
||||
'GET', os.path.join(
|
||||
self.url,
|
||||
'vnflcm/v1/vnf_lcm_op_occs?'
|
||||
'filter=(eq,operationState,STARTING)'),
|
||||
json=self.vnflcm_op_occs_obj, headers=self.header)
|
||||
|
||||
actual_columns, data = self.list_vnflcm_op_occ.take_action(parsed_args)
|
||||
|
||||
headers, columns = tacker_osc_utils.get_column_definitions(
|
||||
self.list_vnflcm_op_occ.get_attributes(), long_listing=True)
|
||||
|
||||
expected_data = []
|
||||
for vnflcm_op_occ_obj in self.vnflcm_op_occs_obj:
|
||||
expected_data.append(vnflcm_op_occs_fakes.get_vnflcm_op_occ_data(
|
||||
vnflcm_op_occ_obj, columns=columns))
|
||||
|
||||
self.assertItemsEqual(_get_columns_vnflcm_op_occs(action='list'),
|
||||
actual_columns)
|
||||
self.assertListItemsEqual(expected_data, list(data))
|
||||
|
||||
def test_take_action_with_incorrect_filter(self):
|
||||
|
||||
parsed_args = self.check_parser(
|
||||
self.list_vnflcm_op_occ,
|
||||
["--filter", '(operationState)'],
|
||||
[('filter', '(operationState)')])
|
||||
|
||||
url = os.path.join(
|
||||
self.url,
|
||||
'vnflcm/v1/vnf_lcm_op_occs?filter=(operationState)')
|
||||
self.requests_mock.register_uri(
|
||||
'POST', url, headers=self.header, status_code=400, json={})
|
||||
|
||||
self.assertRaises(exceptions.TackerClientException,
|
||||
self.list_vnflcm_op_occ.take_action,
|
||||
parsed_args)
|
||||
|
||||
def test_take_action_internal_server_error(self):
|
||||
|
||||
parsed_args = self.check_parser(
|
||||
self.list_vnflcm_op_occ,
|
||||
["--filter", '(eq,operationState,STARTING)'],
|
||||
[('filter', '(eq,operationState,STARTING)')])
|
||||
|
||||
url = os.path.join(
|
||||
self.url,
|
||||
'vnflcm/v1/vnf_lcm_op_occs?'
|
||||
'filter=(eq,operationState,STARTING)')
|
||||
self.requests_mock.register_uri(
|
||||
'POST', url, headers=self.header, status_code=500, json={})
|
||||
|
||||
self.assertRaises(exceptions.TackerClientException,
|
||||
self.list_vnflcm_op_occ.take_action,
|
||||
parsed_args)
|
||||
|
@@ -16,7 +16,7 @@ from oslo_utils import uuidutils
|
||||
from tackerclient.osc import utils as tacker_osc_utils
|
||||
|
||||
|
||||
def vnflcm_op_occ_response(attrs=None):
|
||||
def vnflcm_op_occ_response(attrs=None, action=''):
|
||||
"""Create a fake vnflcm op occurrence.
|
||||
|
||||
:param Dictionary attrs:
|
||||
@@ -33,18 +33,36 @@ def vnflcm_op_occ_response(attrs=None):
|
||||
"stateEnteredTime": "2018-12-22T16:59:45.187Z",
|
||||
"startTime": "2018-12-22T16:59:45.187Z",
|
||||
"vnfInstanceId": "376f37f3-d4e9-4d41-8e6a-9b0ec98695cc",
|
||||
"grantId": "",
|
||||
"operation": "INSTANTIATE",
|
||||
"isAutomaticInvocation": "true",
|
||||
"operationParams": {
|
||||
"flavourId": "default",
|
||||
"instantiationLevelId": "n-mme-min"
|
||||
},
|
||||
"isCancelPending": "true",
|
||||
"cancelMode": "",
|
||||
"error": {
|
||||
"status": "500",
|
||||
"detail": "internal server error"
|
||||
},
|
||||
"resourceChanges": [],
|
||||
"changedInfo": [],
|
||||
"changedExtConnectivity": [],
|
||||
"_links": {
|
||||
"self": ""
|
||||
}
|
||||
}
|
||||
|
||||
if action == 'fail':
|
||||
fail_not_needed_columns = [
|
||||
'grantId', 'operationParams',
|
||||
'cancelMode', 'resourceChanges', 'changedInfo',
|
||||
'changedExtConnectivity']
|
||||
|
||||
for key in fail_not_needed_columns:
|
||||
del dummy_vnf_lcm_op_occ[key]
|
||||
|
||||
# Overwrite default attributes.
|
||||
dummy_vnf_lcm_op_occ.update(attrs)
|
||||
|
||||
@@ -57,7 +75,10 @@ def get_vnflcm_op_occ_data(vnf_lcm_op_occ, columns=None):
|
||||
:return:
|
||||
A tuple object sorted based on the name of the columns.
|
||||
"""
|
||||
complex_attributes = ['error', 'links']
|
||||
complex_attributes = [
|
||||
'operationParams', 'error', 'resourceChanges',
|
||||
'changedInfo', 'changedExtConnectivity', 'links']
|
||||
|
||||
for attribute in complex_attributes:
|
||||
if vnf_lcm_op_occ.get(attribute):
|
||||
vnf_lcm_op_occ.update(
|
||||
|
@@ -876,6 +876,7 @@ class VnfLCMClient(ClientBase):
|
||||
|
||||
vnf_instances_path = '/vnflcm/v1/vnf_instances'
|
||||
vnf_instance_path = '/vnflcm/v1/vnf_instances/%s'
|
||||
vnf_lcm_op_occurrences_path = '/vnflcm/v1/vnf_lcm_op_occs'
|
||||
vnf_lcm_op_occs_path = '/vnflcm/v1/vnf_lcm_op_occs/%s'
|
||||
|
||||
def build_action(self, action):
|
||||
@@ -940,6 +941,12 @@ class VnfLCMClient(ClientBase):
|
||||
def retry_vnf_instance(self, occ_id):
|
||||
return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id)
|
||||
|
||||
@APIParamsCall
|
||||
def list_vnf_lcm_op_occs(self, retrieve_all=True, **_params):
|
||||
vnf_lcm_op_occs = self.list(None, self.vnf_lcm_op_occurrences_path,
|
||||
retrieve_all, **_params)
|
||||
return vnf_lcm_op_occs
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Unified interface to interact with multiple applications of tacker service.
|
||||
@@ -1245,3 +1252,7 @@ class Client(object):
|
||||
|
||||
def download_vnf_package(self, vnf_package):
|
||||
return self.vnf_package_client.download_vnf_package(vnf_package)
|
||||
|
||||
def list_vnf_lcm_op_occs(self, retrieve_all=True, **_params):
|
||||
return self.vnf_lcm_client.list_vnf_lcm_op_occs(
|
||||
retrieve_all=retrieve_all, **_params)
|
||||
|
Reference in New Issue
Block a user