ba372bfcfd
This patch will do 2 things: 1. Add 'vnf_ids' and 'vnffg_ids' fields as outputs from network service list command. Users can know which VNFs or VNFFG, that belongs to specific NS. 2. Add 'ns_id' fields to VNFFG list command, that shows which network service the current VNFFG belongs to it. Partially-implements: blueprint vnffg-ns Change-Id: If6c5550f94e676fb2062e32ddc069acd5dfb6490
542 lines
20 KiB
Python
542 lines
20 KiB
Python
# 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="<VNFFG>",
|
|
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="<VNFFG>",
|
|
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="<VNFFG>",
|
|
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="<NFP>",
|
|
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="<Classifier ID>",
|
|
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="<SFC>",
|
|
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)
|