diff --git a/tackerclient/osc/v1/vnfpkgm/vnf_package.py b/tackerclient/osc/v1/vnfpkgm/vnf_package.py index b5b8e87d..322f0e42 100644 --- a/tackerclient/osc/v1/vnfpkgm/vnf_package.py +++ b/tackerclient/osc/v1/vnfpkgm/vnf_package.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from functools import reduce import logging import sys @@ -27,21 +28,11 @@ from tackerclient.osc import utils as tacker_osc_utils LOG = logging.getLogger(__name__) -_attr_map = ( - ('id', 'ID', tacker_osc_utils.LIST_BOTH), - ('vnfProductName', 'VNF Product Name', tacker_osc_utils.LIST_BOTH), - ('onboardingState', 'Onboarding State', tacker_osc_utils.LIST_BOTH), - ('usageState', 'Usage State', tacker_osc_utils.LIST_BOTH), - ('operationalState', 'Operational State', tacker_osc_utils.LIST_BOTH), - ('userDefinedData', 'User Defined Data', tacker_osc_utils.LIST_BOTH) -) - _mixed_case_fields = ('usageState', 'onboardingState', 'operationalState', 'vnfProductName', 'softwareImages', 'userDefinedData', 'vnfdId', 'vnfdVersion', 'vnfSoftwareVersion', - 'vnfProvider', 'artifactPath', 'imagePath', - 'diskFormat', 'userMetadata') + 'vnfProvider') def _get_columns(vnf_package_obj): @@ -61,7 +52,8 @@ def _get_columns(vnf_package_obj): 'vnfSoftwareVersion': 'VNF Software Version', 'vnfProductName': 'VNF Product Name', 'vnfdId': 'VNFD ID', - 'vnfdVersion': 'VNFD Version' + 'vnfdVersion': 'VNFD Version', + 'checksum': 'Checksum' }) return sdk_utils.get_osc_show_columns_for_sdk_resource(vnf_package_obj, @@ -100,19 +92,104 @@ class CreateVnfPackage(command.ShowOne): class ListVnfPackage(command.Lister): - _description = _("List VNF Package") + _description = _("List VNF Packages") def get_parser(self, prog_name): LOG.debug('get_parser(%s)', prog_name) parser = super(ListVnfPackage, self).get_parser(prog_name) + parser.add_argument( + "--filter", + metavar="", + help=_("Atrribute-based-filtering parameters"), + ) + fields_exclusive_group = parser.add_mutually_exclusive_group( + required=False) + fields_exclusive_group.add_argument( + "--all_fields", + action="store_true", + default=False, + help=_("Include all complex attributes in the response"), + ) + 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"), + ) + parser.add_argument( + "--exclude_default", + action="store_true", + default=False, + help=_("Indicates to exclude all complex attributes" + " from the response. This argument can be used alone or" + " with --fields and --filter. For all other combinations" + " tacker server will throw bad request error"), + ) return parser + def case_modify(self, field): + return reduce( + lambda x, y: x + (' ' if y.isupper() else '') + y, field).title() + + def get_attributes(self, extra_fields=None, all_fields=False, + exclude_fields=None): + fields = ['id', 'vnfProductName', 'onboardingState', + 'usageState', 'operationalState'] + complex_fields = ['checksum', 'softwareImages', 'userDefinedData'] + simple_fields = ['vnfdVersion', 'vnfProvider', 'vnfSoftwareVersion', + 'vnfdId', '_links'] + + if extra_fields: + fields.extend(extra_fields) + + if exclude_fields: + fields.extend([field for field in complex_fields + if field not in exclude_fields]) + if all_fields: + fields.extend(complex_fields) + fields.extend(simple_fields) + + attrs = [] + for field in fields: + if field == '_links': + attrs.extend([(field, 'Links', tacker_osc_utils.LIST_BOTH)]) + else: + attrs.extend([(field, self.case_modify(field), + tacker_osc_utils.LIST_BOTH)]) + + return tuple(attrs) + def take_action(self, parsed_args): _params = {} + extra_fields = [] + exclude_fields = [] + all_fields = False + 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) + if parsed_args.exclude_default: + _params['exclude_default'] = None + if parsed_args.all_fields: + _params['all_fields'] = None + all_fields = True + client = self.app.client_manager.tackerclient data = client.list_vnf_packages(**_params) headers, columns = tacker_osc_utils.get_column_definitions( - _attr_map, long_listing=True) + self.get_attributes(extra_fields, all_fields, exclude_fields), + long_listing=True) return (headers, (utils.get_dict_properties( s, columns, mixed_case_fields=_mixed_case_fields, diff --git a/tackerclient/tests/unit/osc/v1/test_vnf_package.py b/tackerclient/tests/unit/osc/v1/test_vnf_package.py index 5dc86a9b..be6e6757 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnf_package.py +++ b/tackerclient/tests/unit/osc/v1/test_vnf_package.py @@ -51,17 +51,14 @@ def _get_columns_vnf_package(action='list', vnf_package_obj=None): return columns columns.extend(['ID', 'Onboarding State', 'Operational State', - 'Usage State', 'User Defined Data', 'VNF Product Name']) + 'Usage State', 'User Defined Data', 'Links']) if action in ['show', 'create']: if vnf_package_obj and vnf_package_obj[ 'onboardingState'] == 'ONBOARDED': - columns.extend(['Links', 'VNFD ID', 'VNF Provider', - 'VNF Software Version', 'VNFD Version', - 'Software Images']) - else: - columns.extend(['Links']) - columns.remove('VNF Product Name') + columns.extend(['VNFD ID', 'VNF Provider', 'VNF Software Version', + 'VNFD Version', 'Software Images', + 'VNF Product Name', 'Checksum']) return columns @@ -106,37 +103,168 @@ class TestCreateVnfPackage(TestVnfPackage): json=json, headers=self.header) columns, data = (self.create_vnf_package.take_action(parsed_args)) - self.assertCountEqual(_get_columns_vnf_package(action='create'), - columns) + self.assertCountEqual(_get_columns_vnf_package(), columns) self.assertCountEqual(vnf_package_fakes.get_vnf_package_data(json), data) +@ddt.ddt class TestListVnfPackage(TestVnfPackage): - _vnf_packages = vnf_package_fakes.create_vnf_packages(count=3) - def setUp(self): super(TestListVnfPackage, self).setUp() self.list_vnf_package = vnf_package.ListVnfPackage( self.app, self.app_args, cmd_name='vnf package list') + self._vnf_packages = self._get_vnf_packages() - def test_take_action(self): + def _get_vnf_packages(self, onboarded_vnf_package=False): + return vnf_package_fakes.create_vnf_packages( + count=3, onboarded_vnf_package=onboarded_vnf_package) + + def get_list_columns(self, all_fields=False, exclude_fields=None, + extra_fields=None): + + columns = ['Id', 'Vnf Product Name', 'Onboarding State', 'Usage State', + 'Operational State'] + complex_columns = ['Checksum', 'Software Images', 'User Defined Data'] + simple_columns = ['Vnfd Version', 'Vnf Provider', 'Vnfd Id', 'Links', + 'Vnf Software Version'] + + if extra_fields: + columns.extend(extra_fields) + + if exclude_fields: + columns.extend([field for field in complex_columns + if field not in exclude_fields]) + if all_fields: + columns.extend(complex_columns) + columns.extend(simple_columns) + + return columns + + def _get_mock_response_for_list_vnf_packages( + self, filter_attribute, json=None): + self.requests_mock.register_uri( + 'GET', self.url + '/vnfpkgm/v1/vnf_packages?' + filter_attribute, + json=json if json else self._get_vnf_packages(), + headers=self.header) + + def test_take_action_default_fields(self): parsed_args = self.check_parser(self.list_vnf_package, [], []) self.requests_mock.register_uri( 'GET', self.url + '/vnfpkgm/v1/vnf_packages', json=self._vnf_packages, headers=self.header) actual_columns, data = self.list_vnf_package.take_action(parsed_args) - expected_data = [] headers, columns = tacker_osc_utils.get_column_definitions( - vnf_package._attr_map, long_listing=True) + self.list_vnf_package.get_attributes(), long_listing=True) for vnf_package_obj in self._vnf_packages['vnf_packages']: expected_data.append(vnf_package_fakes.get_vnf_package_data( vnf_package_obj, columns=columns, list_action=True)) + self.assertCountEqual(self.get_list_columns(), actual_columns) + self.assertCountEqual(expected_data, list(data)) - self.assertCountEqual(_get_columns_vnf_package(), actual_columns) + @ddt.data('all_fields', 'exclude_default') + def test_take_action(self, arg): + parsed_args = self.check_parser( + self.list_vnf_package, + ["--" + arg, "--filter", '(eq,onboardingState,ONBOARDED)'], + [(arg, True), ('filter', '(eq,onboardingState,ONBOARDED)')]) + vnf_packages = self._get_vnf_packages(onboarded_vnf_package=True) + self._get_mock_response_for_list_vnf_packages( + 'filter=(eq,onboardingState,ONBOARDED)&' + arg, json=vnf_packages) + + actual_columns, data = self.list_vnf_package.take_action(parsed_args) + expected_data = [] + kwargs = {arg: True} if arg == 'all_fields' else {} + headers, columns = tacker_osc_utils.get_column_definitions( + self.list_vnf_package.get_attributes(**kwargs), long_listing=True) + + for vnf_package_obj in vnf_packages['vnf_packages']: + expected_data.append(vnf_package_fakes.get_vnf_package_data( + vnf_package_obj, columns=columns, list_action=True, **kwargs)) + + self.assertCountEqual(self.get_list_columns(**kwargs), actual_columns) + self.assertCountEqual(expected_data, list(data)) + + def test_take_action_with_exclude_fields(self): + parsed_args = self.check_parser( + self.list_vnf_package, + ["--exclude_fields", 'softwareImages,checksum,userDefinedData', + "--filter", '(eq,onboardingState,ONBOARDED)'], + [('exclude_fields', 'softwareImages,checksum,userDefinedData'), + ('filter', '(eq,onboardingState,ONBOARDED)')]) + vnf_packages = self._get_vnf_packages(onboarded_vnf_package=True) + updated_vnf_packages = {'vnf_packages': []} + for vnf_pkg in vnf_packages['vnf_packages']: + vnf_pkg.pop('softwareImages') + vnf_pkg.pop('checksum') + vnf_pkg.pop('userDefinedData') + updated_vnf_packages['vnf_packages'].append(vnf_pkg) + self._get_mock_response_for_list_vnf_packages( + 'filter=(eq,onboardingState,ONBOARDED)&' + 'exclude_fields=softwareImages,checksum,userDefinedData', + json=updated_vnf_packages) + + actual_columns, data = self.list_vnf_package.take_action(parsed_args) + expected_data = [] + headers, columns = tacker_osc_utils.get_column_definitions( + self.list_vnf_package.get_attributes( + exclude_fields=['softwareImages', 'checksum', + 'userDefinedData']), + long_listing=True) + + for vnf_package_obj in updated_vnf_packages['vnf_packages']: + expected_data.append(vnf_package_fakes.get_vnf_package_data( + vnf_package_obj, columns=columns, list_action=True)) + expected_columns = self.get_list_columns( + exclude_fields=['Software Images', 'Checksum', + 'User Defined Data']) + self.assertCountEqual(expected_columns, actual_columns) + self.assertCountEqual(expected_data, list(data)) + + @ddt.data((['--all_fields', '--fields', 'softwareImages'], + [('all_fields', True), ('fields', 'softwareImages')]), + (['--all_fields', '--exclude_fields', 'checksum'], + [('all_fields', True), ('exclude_fields', 'checksum')]), + (['--fields', 'softwareImages', '--exclude_fields', 'checksum'], + [('fields', 'softwareImages'), ('exclude_fields', 'checksum')])) + @ddt.unpack + def test_take_action_with_invalid_combination(self, arglist, verifylist): + self.assertRaises(base.ParserException, self.check_parser, + self.list_vnf_package, arglist, verifylist) + + def test_take_action_with_valid_combination(self): + parsed_args = self.check_parser( + self.list_vnf_package, + ["--fields", 'softwareImages,checksum', "--exclude_default"], + [('fields', 'softwareImages,checksum'), ('exclude_default', True)]) + vnf_packages = self._get_vnf_packages(onboarded_vnf_package=True) + updated_vnf_packages = {'vnf_packages': []} + for vnf_pkg in vnf_packages['vnf_packages']: + vnf_pkg.pop('userDefinedData') + updated_vnf_packages['vnf_packages'].append(vnf_pkg) + + self._get_mock_response_for_list_vnf_packages( + 'exclude_default&fields=softwareImages,checksum', + json=updated_vnf_packages) + + actual_columns, data = self.list_vnf_package.take_action(parsed_args) + expected_data = [] + headers, columns = tacker_osc_utils.get_column_definitions( + self.list_vnf_package.get_attributes( + extra_fields=['softwareImages', 'checksum']), + long_listing=True) + + for vnf_package_obj in updated_vnf_packages['vnf_packages']: + expected_data.append(vnf_package_fakes.get_vnf_package_data( + vnf_package_obj, columns=columns, list_action=True, + exclude_default=True)) + + self.assertCountEqual(self.get_list_columns( + extra_fields=['Software Images', 'Checksum']), + actual_columns) self.assertCountEqual(expected_data, list(data)) @@ -160,6 +288,8 @@ class TestShowVnfPackage(TestVnfPackage): self.requests_mock.register_uri('GET', url, json=vnf_package_obj, headers=self.header) columns, data = (self.show_vnf_package.take_action(parsed_args)) + self.assertEqual(sorted(_get_columns_vnf_package( + vnf_package_obj=vnf_package_obj, action='show')), sorted(columns)) self.assertCountEqual(_get_columns_vnf_package( vnf_package_obj=vnf_package_obj, action='show'), columns) self.assertCountEqual( diff --git a/tackerclient/tests/unit/osc/v1/vnf_package_fakes.py b/tackerclient/tests/unit/osc/v1/vnf_package_fakes.py index 978a4b3a..b7967297 100644 --- a/tackerclient/tests/unit/osc/v1/vnf_package_fakes.py +++ b/tackerclient/tests/unit/osc/v1/vnf_package_fakes.py @@ -63,6 +63,10 @@ def vnf_package_obj(attrs=None, onboarded_state=False): "imagePath": "string" } ], + "checksum": { + "algorithm": "string", + "hash": "string" + }, "onboardingState": "ONBOARDED", "operationalState": "ENABLED", "usageState": "IN_USE", @@ -84,7 +88,7 @@ def vnf_package_obj(attrs=None, onboarded_state=False): return fake_vnf_package -def get_vnf_package_data(vnf_package, list_action=False, columns=None): +def get_vnf_package_data(vnf_package, **kwargs): """Get the vnf package data from a FakeVnfPackage dict object. :param vnf_package: @@ -96,8 +100,7 @@ def get_vnf_package_data(vnf_package, list_action=False, columns=None): 'CREATED', 'DISABLED', 'NOT_IN_USE', {'Test_key': 'Test_value'}] """ - if list_action: - vnf_package.pop('_links') + if kwargs.get('list_action'): # In case of List VNF packages we get empty string as data for # 'vnfProductName' if onboardingState is CREATED. Hence to match # up with actual data we are adding here empty string. @@ -105,13 +108,13 @@ def get_vnf_package_data(vnf_package, list_action=False, columns=None): vnf_package['vnfProductName'] = '' # return the list of data as per column order - if columns: - return tuple([vnf_package[key] for key in columns]) + if kwargs.get('columns'): + return tuple([vnf_package[key] for key in kwargs.get('columns')]) return tuple([vnf_package[key] for key in sorted(vnf_package.keys())]) -def create_vnf_packages(count=2): +def create_vnf_packages(count=2, onboarded_vnf_package=False): """Create multiple fake vnf packages. :param Dictionary attrs: @@ -124,7 +127,8 @@ def create_vnf_packages(count=2): vnf_packages = [] for i in range(0, count): unique_id = uuidutils.generate_uuid() - vnf_packages.append(vnf_package_obj(attrs={'id': unique_id})) + vnf_packages.append(vnf_package_obj( + attrs={'id': unique_id}, onboarded_state=onboarded_vnf_package)) return {'vnf_packages': vnf_packages} diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py index 427f3527..fe136ace 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -191,12 +191,32 @@ class ClientBase(object): action = self.action_prefix + action return action + def _build_params_query(self, params=None): + flag_params = [] + keyval_params = {} + for key, value in params.items(): + if value is None: + flag_params.append(key) + else: + keyval_params[key] = value + + flags_encoded = utils.safe_encode_list(flag_params) \ + if flag_params else "" + keyval_encoded = utils.safe_encode_dict(keyval_params) \ + if keyval_params else "" + + query = "" + for flag in flags_encoded: + query = query + urlparse.quote_plus(flag) + '&' + query = query + urlparse.urlencode(keyval_encoded, doseq=1) + return query.strip('&') + def do_request(self, method, action, body=None, headers=None, params=None): action = self.build_action(action) # Add format and tenant_id if type(params) is dict and params: - params = utils.safe_encode_dict(params) - action += '?' + urlparse.urlencode(params, doseq=1) + query = self._build_params_query(params) + action += '?' + query if body or body == {}: body = self.serialize(body) @@ -1109,7 +1129,8 @@ class Client(object): def create_vnf_package(self, body): return self.vnf_package_client.create_vnf_package(body) - def list_vnf_packages(self, retrieve_all=True, **_params): + def list_vnf_packages(self, retrieve_all=True, query_parameter=None, + **_params): return self.vnf_package_client.list_vnf_packages( retrieve_all=retrieve_all, **_params)