diff --git a/setup.cfg b/setup.cfg index cbd5ef15..c2c12f74 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py b/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py index 6e519c3c..3cb3715e 100644 --- a/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py +++ b/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py @@ -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="", + help=_("Attribute-based-filtering parameters"), + ) + fields_exclusive_group = parser.add_mutually_exclusive_group( + required=False) + fields_exclusive_group.add_argument( + "--fields", + metavar="", + help=_("Complex attributes to be included into the response"), + ) + fields_exclusive_group.add_argument( + "--exclude-fields", + metavar="", + 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) diff --git a/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py b/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py index 3906a4f8..ea132642 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py @@ -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) diff --git a/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py b/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py index bbf6202c..22fb2c8b 100644 --- a/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py +++ b/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py @@ -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( diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py index 76745c81..c5fabfc6 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -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)