# Copyright 2018 OpenStack Foundation. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import yaml from osc_lib.command import command from osc_lib import utils from tackerclient.common import exceptions from tackerclient.i18n import _ from tackerclient.osc import sdk_utils from tackerclient.osc import utils as tacker_osc_utils from tackerclient.tacker import v1_0 as tackerV10 _VNFFG = 'vnffg' # VNF Forwarding Graph _NFP = 'nfp' # Network Forwarding Path _SFC = 'sfc' # Service Function Chain _FC = 'classifier' # Flow Classifier nfps_path = '/nfps' fcs_path = '/classifiers' sfcs_path = '/sfcs' DEFAULT_ERROR_REASON_LENGTH = 100 _attr_map_vnffg = ( ('id', 'ID', tacker_osc_utils.LIST_BOTH), ('name', 'Name', tacker_osc_utils.LIST_BOTH), ('ns_id', 'NS ID', tacker_osc_utils.LIST_BOTH), ('vnffgd_id', 'VNFFGD ID', tacker_osc_utils.LIST_BOTH), ('status', 'Status', tacker_osc_utils.LIST_BOTH), ('description', 'Description', tacker_osc_utils.LIST_LONG_ONLY), ) _attr_map_nfp = ( ('id', 'ID', tacker_osc_utils.LIST_BOTH), ('name', 'Name', tacker_osc_utils.LIST_BOTH), ('status', 'Status', tacker_osc_utils.LIST_BOTH), ('vnffg_id', 'VNFFG ID', tacker_osc_utils.LIST_BOTH), ('path_id', 'Path ID', tacker_osc_utils.LIST_BOTH), ) _attr_map_sfc = ( ('id', 'ID', tacker_osc_utils.LIST_BOTH), ('status', 'Status', tacker_osc_utils.LIST_BOTH), ('nfp_id', 'NFP ID', tacker_osc_utils.LIST_BOTH), ) _attr_map_fc = ( ('id', 'ID', tacker_osc_utils.LIST_BOTH), ('name', 'Name', tacker_osc_utils.LIST_BOTH), ('status', 'Status', tacker_osc_utils.LIST_BOTH), ('nfp_id', 'NFP ID', tacker_osc_utils.LIST_BOTH), ('chain_id', 'Chain ID', tacker_osc_utils.LIST_BOTH), ) _formatters = { 'attributes': tacker_osc_utils.format_dict_with_indention, 'match': tacker_osc_utils.format_dict_with_indention, 'chain': tacker_osc_utils.format_dict_with_indention, } def _get_columns(item): column_map = { 'tenant_id': 'project_id', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) class CreateVNFFG(command.ShowOne): _description = _("Create a new VNFFG.") def get_parser(self, prog_name): parser = super(CreateVNFFG, self).get_parser(prog_name) parser.add_argument( 'name', metavar='NAME', help=_('Set a name for the VNFFG')) parser.add_argument( '--tenant-id', metavar='TENANT_ID', help=_('The owner tenant ID')) vnffgd_group = parser.add_mutually_exclusive_group(required=True) vnffgd_group.add_argument( '--vnffgd-id', help=_('VNFFGD ID to use as template to create VNFFG')) vnffgd_group.add_argument( '--vnffgd-name', help=_('VNFFGD Name to use as template to create VNFFG')) vnffgd_group.add_argument( '--vnffgd-template', help=_('VNFFGD file to create VNFFG')) parser.add_argument( '--vnf-mapping', help=_('List of logical VNFD name to VNF instance name mapping. ' 'Example: VNF1:my_vnf1,VNF2:my_vnf2')) parser.add_argument( '--symmetrical', action='store_true', default=False, help=_('Should a reverse path be created for the NFP ' '(True or False)')) parser.add_argument( '--param-file', help=_('YAML file with specific VNFFG parameters')) parser.add_argument( '--description', help=_('Set a description for the VNFFG')) return parser def args2body(self, parsed_args): body = {_VNFFG: {}} body[_VNFFG]['attributes'] = {} client = self.app.client_manager.tackerclient if parsed_args.vnf_mapping: _vnf_mapping = dict() _vnf_mappings = parsed_args.vnf_mapping.split(",") for mapping in _vnf_mappings: vnfd_name, vnf = mapping.split(":", 1) _vnf_mapping[vnfd_name] = \ tackerV10.find_resourceid_by_name_or_id( client, 'vnf', vnf) parsed_args.vnf_mapping = _vnf_mapping if parsed_args.vnffgd_name: _id = tackerV10.find_resourceid_by_name_or_id( client, 'vnffgd', parsed_args.vnffgd_name) parsed_args.vnffgd_id = _id elif parsed_args.vnffgd_template: with open(parsed_args.vnffgd_template) as f: template = f.read() try: template = yaml.load(template, Loader=yaml.SafeLoader) except yaml.YAMLError as e: raise exceptions.InvalidInput(e) if not template: raise exceptions.InvalidInput('The vnffgd file is empty') body[_VNFFG]['vnffgd_template'] = template if parsed_args.param_file: with open(parsed_args.param_file) as f: param_yaml = f.read() try: param_yaml = yaml.load( param_yaml, Loader=yaml.SafeLoader) except yaml.YAMLError as e: raise exceptions.InvalidInput(e) if not param_yaml: raise exceptions.InvalidInput('The parameter file is empty') body[_VNFFG]['attributes'] = {'param_values': param_yaml} tackerV10.update_dict(parsed_args, body[_VNFFG], ['tenant_id', 'name', 'vnffgd_id', 'symmetrical', 'vnf_mapping', 'description']) return body def take_action(self, parsed_args): client = self.app.client_manager.tackerclient vnffg = client.create_vnffg(self.args2body(parsed_args)) display_columns, columns = _get_columns(vnffg[_VNFFG]) data = utils.get_item_properties( sdk_utils.DictModel(vnffg[_VNFFG]), columns, formatters=_formatters) return (display_columns, data) class DeleteVNFFG(command.Command): _description = _("Delete VNFFG(s).") def get_parser(self, prog_name): parser = super(DeleteVNFFG, self).get_parser(prog_name) parser.add_argument( _VNFFG, metavar="", nargs="+", help=_("VNFFG(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient failure = False deleted_ids = [] failed_items = {} for resource_id in parsed_args.vnffg: try: obj = tackerV10.find_resourceid_by_name_or_id( client, _VNFFG, resource_id) client.delete_vnffg(obj) deleted_ids.append(resource_id) except Exception as e: failure = True failed_items[resource_id] = e if failure: msg = '' if deleted_ids: msg = (_('Successfully deleted %(resource)s(s):' ' %(deleted_list)s') % {'deleted_list': ', '.join(deleted_ids), 'resource': _VNFFG}) err_msg = _("\n\nUnable to delete the below" " %s(s):") % _VNFFG for failed_id, error in failed_items.iteritems(): err_msg += (_('\n Cannot delete %(failed_id)s: %(error)s') % {'failed_id': failed_id, 'error': error}) msg += err_msg raise exceptions.CommandError(msg) else: print((_('All specified %(resource)s(s) deleted successfully') % {'resource': _VNFFG})) return class UpdateVNFFG(command.ShowOne): _description = _("Update VNFFG.") def get_parser(self, prog_name): parser = super(UpdateVNFFG, self).get_parser(prog_name) parser.add_argument( _VNFFG, metavar="", help=_('VNFFG to update (name or ID)')) parser.add_argument( '--vnffgd-template', help=_('VNFFGD file to update VNFFG')) parser.add_argument( '--vnf-mapping', help=_('List of logical VNFD name to VNF instance name mapping. ' 'Example: VNF1:my_vnf1,VNF2:my_vnf2')) parser.add_argument( '--symmetrical', action='store_true', default=False, help=_('Should a reverse path be created for the NFP')) parser.add_argument( '--description', help=_('Set a description for the VNFFG')) return parser def args2body(self, parsed_args): body = {_VNFFG: {}} body[_VNFFG]['attributes'] = {} client = self.app.client_manager.tackerclient if parsed_args.vnf_mapping: _vnf_mapping = dict() _vnf_mappings = parsed_args.vnf_mapping.split(",") for mapping in _vnf_mappings: vnfd_name, vnf = mapping.split(":", 1) _vnf_mapping[vnfd_name] = \ tackerV10.find_resourceid_by_name_or_id( client, 'vnf', vnf) parsed_args.vnf_mapping = _vnf_mapping if parsed_args.vnffgd_template: with open(parsed_args.vnffgd_template) as f: template = f.read() try: template = yaml.load( template, Loader=yaml.SafeLoader) except yaml.YAMLError as e: raise exceptions.InvalidInput(e) if not template: raise exceptions.InvalidInput('The vnffgd file is empty') body[_VNFFG]['vnffgd_template'] = template if parsed_args.param_file: with open(parsed_args.param_file) as f: param_yaml = f.read() try: param_yaml = yaml.load( param_yaml, Loader=yaml.SafeLoader) except yaml.YAMLError as e: raise exceptions.InvalidInput(e) if not param_yaml: raise exceptions.InvalidInput('The parameter file is empty') body[_VNFFG]['attributes'] = {'param_values': param_yaml} tackerV10.update_dict(parsed_args, body[self.resource], ['vnf_mapping', 'symmetrical', 'description']) return body def take_action(self, parsed_args): client = self.app.client_manager.tackerclient obj_id = tackerV10.find_resourceid_by_name_or_id( client, _VNFFG, parsed_args.vnffg) vnffg = client.update_vnffg(obj_id, self.args2body(parsed_args)) display_columns, columns = _get_columns(vnffg[_VNFFG]) data = utils.get_item_properties( sdk_utils.DictModel(vnffg[_VNFFG]), columns, formatters=_formatters) return (display_columns, data) class ListVNFFG(command.Lister): _description = ("List VNFFG(s) that belong to a given tenant.") def get_parser(self, prog_name): parser = super(ListVNFFG, self).get_parser(prog_name) parser.add_argument( '--long', action='store_true', help=_('List additional fields in output') ) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient data = client.list_vnffgs() headers, columns = tacker_osc_utils.get_column_definitions( _attr_map_vnffg, long_listing=parsed_args.long) return (headers, (utils.get_dict_properties( s, columns, ) for s in data[_VNFFG + 's'])) class ShowVNFFG(command.ShowOne): _description = _("Display VNFFG details") def get_parser(self, prog_name): parser = super(ShowVNFFG, self).get_parser(prog_name) parser.add_argument( _VNFFG, metavar="", help=_('VNFFG to display (name or ID)') ) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient obj_id = tackerV10.find_resourceid_by_name_or_id( client, _VNFFG, parsed_args.vnffg) obj = client.show_vnffg(obj_id) display_columns, columns = _get_columns(obj[_VNFFG]) data = utils.get_item_properties( sdk_utils.DictModel(obj[_VNFFG]), columns, formatters=_formatters) return (display_columns, data) class ListNFP(command.Lister): _description = ("List NFP(s) that belong to a given tenant.") def get_parser(self, prog_name): parser = super(ListNFP, self).get_parser(prog_name) parser.add_argument( '--vnffg-id', help=_('List NFP(s) with specific VNFFG ID')) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient _params = {} if parsed_args.vnffg_id: _params['vnffg_id'] = parsed_args.vnffg_id nfps = client.list('nfps', nfps_path, True, **_params) for nfp in nfps['nfps']: error_reason = nfp.get('error_reason', None) if error_reason and \ len(error_reason) > DEFAULT_ERROR_REASON_LENGTH: nfp['error_reason'] = error_reason[ :DEFAULT_ERROR_REASON_LENGTH] nfp['error_reason'] += '...' data = {} data['nfps'] = nfps['nfps'] data = client.list_nfps() headers, columns = tacker_osc_utils.get_column_definitions( _attr_map_nfp, long_listing=None) return (headers, (utils.get_dict_properties( s, columns, ) for s in data[_NFP + 's'])) class ShowNFP(command.ShowOne): _description = _("Display NFP details") def get_parser(self, prog_name): parser = super(ShowNFP, self).get_parser(prog_name) parser.add_argument( _NFP, metavar="", help=_('NFP to display (name or ID)') ) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient obj_id = tackerV10.find_resourceid_by_name_or_id( client, _NFP, parsed_args.nfp) obj = client.show_nfp(obj_id) display_columns, columns = _get_columns(obj[_NFP]) data = utils.get_item_properties( sdk_utils.DictModel(obj[_NFP]), columns) return (display_columns, data) class ListFC(command.Lister): _description = ("List flow classifier(s) that belong to a given tenant.") def get_parser(self, prog_name): parser = super(ListFC, self).get_parser(prog_name) parser.add_argument( '--nfp-id', help=_('List flow classifier(s) with specific nfp id')) parser.add_argument( '--tenant-id', metavar='TENANT_ID', help=_('The owner tenant ID or project ID')) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient _params = {} if parsed_args.nfp_id: _params['nfp_id'] = parsed_args.nfp_id if parsed_args.tenant_id: _params['tenant_id'] = parsed_args.tenant_id classifiers = client.list('classifiers', fcs_path, True, **_params) for classifier in classifiers['classifiers']: error_reason = classifier.get('error_reason', None) if error_reason and \ len(error_reason) > DEFAULT_ERROR_REASON_LENGTH: classifier['error_reason'] = error_reason[ :DEFAULT_ERROR_REASON_LENGTH] classifier['error_reason'] += '...' data = {} data['classifiers'] = classifiers['classifiers'] data = client.list_classifiers() headers, columns = tacker_osc_utils.get_column_definitions( _attr_map_fc, long_listing=None) return (headers, (utils.get_dict_properties( s, columns, ) for s in data[_FC + 's'])) class ShowFC(command.ShowOne): _description = _("Display flow classifier details") def get_parser(self, prog_name): parser = super(ShowFC, self).get_parser(prog_name) parser.add_argument( _FC, metavar="", help=_('Flow Classifier to display (name or ID)') ) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient obj_id = tackerV10.find_resourceid_by_name_or_id( client, _FC, parsed_args.classifier) obj = client.show_classifier(obj_id) display_columns, columns = _get_columns(obj[_FC]) data = utils.get_item_properties( sdk_utils.DictModel(obj[_FC]), columns, formatters=_formatters) return (display_columns, data) class ListSFC(command.Lister): _description = ("List SFC(s) that belong to a given tenant.") def get_parser(self, prog_name): parser = super(ListSFC, self).get_parser(prog_name) parser.add_argument( '--nfp-id', help=_('List SFC(s) with specific nfp id')) parser.add_argument( '--tenant-id', metavar='TENANT_ID', help=_('The owner tenant ID or project ID')) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient _params = {} if parsed_args.nfp_id: _params['nfp_id'] = parsed_args.nfp_id if parsed_args.tenant_id: _params['tenant_id'] = parsed_args.tenant_id sfcs = client.list('sfcs', sfcs_path, True, **_params) for chain in sfcs['sfcs']: error_reason = chain.get('error_reason', None) if error_reason and \ len(error_reason) > DEFAULT_ERROR_REASON_LENGTH: chain['error_reason'] = error_reason[ :DEFAULT_ERROR_REASON_LENGTH] chain['error_reason'] += '...' data = {} data['sfcs'] = sfcs['sfcs'] headers, columns = tacker_osc_utils.get_column_definitions( _attr_map_sfc, long_listing=None) return (headers, (utils.get_dict_properties( s, columns, ) for s in data[_SFC + 's'])) class ShowSFC(command.ShowOne): _description = _("Display SFC details") def get_parser(self, prog_name): parser = super(ShowSFC, self).get_parser(prog_name) parser.add_argument( _SFC, metavar="", help=_('SFC to display (name or ID)') ) return parser def take_action(self, parsed_args): client = self.app.client_manager.tackerclient obj_id = tackerV10.find_resourceid_by_name_or_id( client, _SFC, parsed_args.sfc) obj = client.show_sfc(obj_id) display_columns, columns = _get_columns(obj[_SFC]) data = utils.get_item_properties( sdk_utils.DictModel(obj[_SFC]), columns, formatters=_formatters) return (display_columns, data)