python-tackerclient/tackerclient/osc/v1/vnflcm/vnflcm.py

586 lines
21 KiB
Python

# Copyright (C) 2020 NTT DATA
# 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 logging
import time
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
_attr_map = (
('id', 'ID', tacker_osc_utils.LIST_BOTH),
('vnfInstanceName', 'VNF Instance Name', tacker_osc_utils.LIST_BOTH),
('instantiationState', 'Instantiation State', tacker_osc_utils.LIST_BOTH),
('vnfProvider', 'VNF Provider', tacker_osc_utils.LIST_BOTH),
('vnfSoftwareVersion', 'VNF Software Version', tacker_osc_utils.LIST_BOTH),
('vnfProductName', 'VNF Product Name', tacker_osc_utils.LIST_BOTH),
('vnfdId', 'VNFD ID', tacker_osc_utils.LIST_BOTH)
)
LOG = logging.getLogger(__name__)
_mixed_case_fields = ('vnfInstanceName', 'vnfInstanceDescription', 'vnfdId',
'vnfProvider', 'vnfProductName', 'vnfSoftwareVersion',
'vnfdVersion', 'instantiationState',
'vimConnectionInfo', 'instantiatedVnfInfo',
'vnfConfigurableProperties')
_VNF_INSTANCE = 'vnf_instance'
VNF_INSTANCE_TERMINATION_TIMEOUT = 300
EXTRA_WAITING_TIME = 10
SLEEP_TIME = 1
formatters = {'vimConnectionInfo': tacker_osc_utils.FormatComplexDataColumn,
'instantiatedVnfInfo': tacker_osc_utils.FormatComplexDataColumn,
'_links': tacker_osc_utils.FormatComplexDataColumn}
def _get_columns(vnflcm_obj, action=None):
column_map = {
'id': 'ID',
'vnfInstanceName': 'VNF Instance Name',
'vnfInstanceDescription': 'VNF Instance Description',
'vnfdId': 'VNFD ID',
'vnfProvider': 'VNF Provider',
'vnfProductName': 'VNF Product Name',
'vnfSoftwareVersion': 'VNF Software Version',
'vnfdVersion': 'VNFD Version',
'instantiationState': 'Instantiation State',
'_links': 'Links',
'vnfConfigurableProperties': 'VNF Configurable Properties',
}
if action == 'show':
if vnflcm_obj['instantiationState'] == 'INSTANTIATED':
column_map.update(
{'instantiatedVnfInfo': 'Instantiated Vnf Info'}
)
column_map.update(
{'vimConnectionInfo': 'VIM Connection Info',
'_links': 'Links'}
)
return sdk_utils.get_osc_show_columns_for_sdk_resource(vnflcm_obj,
column_map)
class CreateVnfLcm(command.ShowOne):
_description = _("Create a new VNF Instance")
def get_parser(self, prog_name):
parser = super(CreateVnfLcm, self).get_parser(prog_name)
parser.add_argument(
'vnfd_id',
metavar="<vnfd-id>",
help=_('Identifier that identifies the VNFD which defines the '
'VNF instance to be created.'))
parser.add_argument(
'--name',
metavar="<vnf-instance-name>",
help=_('Name of the VNF instance to be created.'))
parser.add_argument(
'--description',
metavar="<vnf-instance-description>",
help=_('Description of the VNF instance to be created.'))
parser.add_argument(
'--I',
metavar="<param-file>",
help=_("Instantiate VNF subsequently after it's creation. "
"Specify instantiate request parameters in a json file."))
return parser
def args2body(self, parsed_args, file_path=None):
body = {}
if file_path:
return tacker_osc_utils.jsonfile2body(file_path)
body['vnfdId'] = parsed_args.vnfd_id
if parsed_args.description:
body['vnfInstanceDescription'] = parsed_args.description
if parsed_args.name:
body['vnfInstanceName'] = parsed_args.name
return body
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
vnf = client.create_vnf_instance(self.args2body(parsed_args))
if parsed_args.I:
# Instantiate VNF instance.
result = client.instantiate_vnf_instance(
vnf['id'],
self.args2body(parsed_args, file_path=parsed_args.I))
if not result:
print((_('VNF Instance %(id)s is created and instantiation'
' request has been accepted.') % {'id': vnf['id']}))
display_columns, columns = _get_columns(vnf)
data = utils.get_item_properties(sdk_utils.DictModel(vnf),
columns, formatters=formatters,
mixed_case_fields=_mixed_case_fields)
return (display_columns, data)
class ShowVnfLcm(command.ShowOne):
_description = _("Display VNF instance details")
def get_parser(self, prog_name):
parser = super(ShowVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to display"))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
obj = client.show_vnf_instance(parsed_args.vnf_instance)
display_columns, columns = _get_columns(obj, action='show')
data = utils.get_item_properties(
sdk_utils.DictModel(obj),
columns, mixed_case_fields=_mixed_case_fields,
formatters=formatters)
return (display_columns, data)
class ListVnfLcm(command.Lister):
_description = _("List VNF Instance")
def get_parser(self, prog_name):
parser = super(ListVnfLcm, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
_params = {}
client = self.app.client_manager.tackerclient
vnf_instances = client.list_vnf_instances(**_params)
headers, columns = tacker_osc_utils.get_column_definitions(
_attr_map, long_listing=True)
return (headers,
(utils.get_dict_properties(
s, columns, mixed_case_fields=_mixed_case_fields,
) for s in vnf_instances))
class InstantiateVnfLcm(command.Command):
_description = _("Instantiate a VNF Instance")
def get_parser(self, prog_name):
parser = super(InstantiateVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to instantiate"))
parser.add_argument(
'instantiation_request_file',
metavar="<param-file>",
help=_('Specify instantiate request parameters in a json file.'))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.instantiate_vnf_instance(
parsed_args.vnf_instance, tacker_osc_utils.jsonfile2body(
parsed_args.instantiation_request_file))
if not result:
print((_('Instantiate request for VNF Instance %(id)s has been'
' accepted.') % {'id': parsed_args.vnf_instance}))
class HealVnfLcm(command.Command):
_description = _("Heal VNF Instance")
def get_parser(self, prog_name):
parser = super(HealVnfLcm, self).get_parser(prog_name)
usage_message = ('''%(prog)s [-h] [--cause CAUSE]
[--vnfc-instance <vnfc-instance-id> '''
'''[<vnfc-instance-id> ...]]
[--additional-param-file <additional-param-file>]
-- <vnf-instance>''')
parser.usage = usage_message
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to heal"))
parser.add_argument(
'--cause',
help=_('Specify the reason why a healing procedure is required.'))
parser.add_argument(
'--vnfc-instance',
metavar="<vnfc-instance-id>",
nargs="+",
help=_("List of VNFC instances requiring a healing action.")
)
parser.add_argument(
'--additional-param-file',
metavar="<additional-param-file>",
help=_("Additional parameters passed by the NFVO as input "
"to the healing process."))
return parser
def args2body(self, parsed_args):
body = {}
if parsed_args.cause:
body['cause'] = parsed_args.cause
if parsed_args.vnfc_instance:
body['vnfcInstanceId'] = parsed_args.vnfc_instance
if parsed_args.additional_param_file:
body.update(tacker_osc_utils.jsonfile2body(
parsed_args.additional_param_file))
return body
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.heal_vnf_instance(
parsed_args.vnf_instance, self.args2body(parsed_args))
if not result:
print((_('Heal request for VNF Instance %(id)s has been'
' accepted.') % {'id': parsed_args.vnf_instance}))
class TerminateVnfLcm(command.Command):
_description = _("Terminate a VNF instance")
def get_parser(self, prog_name):
parser = super(TerminateVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to terminate"))
parser.add_argument(
"--termination-type",
default='GRACEFUL',
metavar="<termination-type>",
choices=['GRACEFUL', 'FORCEFUL'],
help=_("Termination type can be 'GRACEFUL' or 'FORCEFUL'. "
"Default is 'GRACEFUL'"))
parser.add_argument(
'--graceful-termination-timeout',
metavar="<graceful-termination-timeout>",
type=int,
help=_('This attribute is only applicable in case of graceful '
'termination. It defines the time to wait for the VNF to be'
' taken out of service before shutting down the VNF and '
'releasing the resources. The unit is seconds.'))
parser.add_argument(
'--D',
action='store_true',
default=False,
help=_("Delete VNF Instance subsequently after it's termination"),
)
return parser
def args2body(self, parsed_args):
body = {}
body['terminationType'] = parsed_args.termination_type
if parsed_args.graceful_termination_timeout:
if parsed_args.termination_type == 'FORCEFUL':
exceptions.InvalidInput(reason='--graceful-termination-timeout'
' argument is invalid for "FORCEFUL"'
' termination')
body['gracefulTerminationTimeout'] = parsed_args.\
graceful_termination_timeout
return body
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.terminate_vnf_instance(parsed_args.vnf_instance,
self.args2body(parsed_args))
if not result:
print(_("Terminate request for VNF Instance '%(id)s' has been"
" accepted.") % {'id': parsed_args.vnf_instance})
if parsed_args.D:
print(_("Waiting for vnf instance to be terminated before "
"deleting"))
self._wait_until_vnf_is_terminated(
client, parsed_args.vnf_instance,
graceful_timeout=parsed_args.graceful_termination_timeout)
result = client.delete_vnf_instance(parsed_args.vnf_instance)
if not result:
print(_("VNF Instance '%(id)s' is deleted successfully") %
{'id': parsed_args.vnf_instance})
def _wait_until_vnf_is_terminated(self, client, vnf_instance_id,
graceful_timeout=None):
# wait until vnf instance 'instantiationState' is set to
# 'NOT_INSTANTIATED'
if graceful_timeout:
# If graceful_termination_timeout is provided,
# terminate vnf will start after this timeout period.
# Hence, it should wait for extra time of 10 seconds
# after this graceful_termination_timeout period.
timeout = graceful_timeout + EXTRA_WAITING_TIME
else:
timeout = VNF_INSTANCE_TERMINATION_TIMEOUT
start_time = int(time.time())
while True:
vnf_instance = client.show_vnf_instance(vnf_instance_id)
if vnf_instance['instantiationState'] == 'NOT_INSTANTIATED':
break
if ((int(time.time()) - start_time) > timeout):
msg = _("Couldn't verify vnf instance is terminated within "
"'%(timeout)s' seconds. Unable to delete vnf instance "
"%(id)s")
raise exceptions.CommandError(
message=msg % {'timeout': timeout, 'id': vnf_instance_id})
time.sleep(SLEEP_TIME)
class DeleteVnfLcm(command.Command):
"""Vnf lcm delete
DeleteVnfLcm class supports bulk deletion of vnf instances, and error
handling.
"""
_description = _("Delete VNF Instance(s)")
def get_parser(self, prog_name):
parser = super(DeleteVnfLcm, self).get_parser(prog_name)
parser.add_argument(
'vnf_instances',
metavar="<vnf-instance>",
nargs="+",
help=_("VNF instance ID(s) to delete"))
return parser
def take_action(self, parsed_args):
error_count = 0
client = self.app.client_manager.tackerclient
vnf_instances = parsed_args.vnf_instances
for vnf_instance in vnf_instances:
try:
client.delete_vnf_instance(vnf_instance)
except Exception as e:
error_count += 1
LOG.error(_("Failed to delete vnf instance with "
"ID '%(vnf)s': %(e)s"),
{'vnf': vnf_instance, 'e': e})
total = len(vnf_instances)
if (error_count > 0):
msg = (_("Failed to delete %(error_count)s of %(total)s "
"vnf instances.") % {'error_count': error_count,
'total': total})
raise exceptions.CommandError(message=msg)
else:
if total > 1:
print(_('All specified vnf instances are deleted '
'successfully'))
else:
print(_("Vnf instance '%s' is deleted "
"successfully") % vnf_instances[0])
class UpdateVnfLcm(command.Command):
_description = _("Update VNF Instance")
def get_parser(self, prog_name):
"""Add arguments to parser.
Args:
prog_name ([string]): program name
Returns:
parser([ArgumentParser]): [description]
"""
parser = super(UpdateVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_('VNF instance ID to update.'))
parser.add_argument(
'--I',
metavar="<param-file>",
help=_("Specify update request parameters in a json file."))
return parser
def args2body(self, file_path=None):
"""Call jsonfile2body to store request body to body(dict)
Args:
file_path ([string], optional): file path of param file(json).
Defaults to None.
Returns:
body ([dict]): Request body is stored
"""
body = {}
if file_path:
return tacker_osc_utils.jsonfile2body(file_path)
return body
def take_action(self, parsed_args):
"""Execute update_vnf_instance and output result comment
Args:
parsed_args ([Namespace]): [description]
"""
client = self.app.client_manager.tackerclient
if parsed_args.I:
# Update VNF instance.
result = client.update_vnf_instance(
parsed_args.vnf_instance,
self.args2body(file_path=parsed_args.I))
if not result:
print((_('Update vnf:%(id)s ') %
{'id': parsed_args.vnf_instance}))
class ScaleVnfLcm(command.Command):
_description = _("Scale a VNF Instance")
def get_parser(self, prog_name):
parser = super(ScaleVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_('VNF instance ID to scale'))
parser.add_argument(
'--number-of-steps',
metavar="<number-of-steps>",
type=int,
help=_("Number of scaling steps to be executed as part of "
"this Scale VNF operation."))
parser.add_argument(
'--additional-param-file',
metavar="<additional-param-file>",
help=_("Additional parameters passed by the NFVO as input "
"to the scaling process."))
scale_require_parameters = parser.add_argument_group(
"require arguments"
)
scale_require_parameters.add_argument(
'--type',
metavar="<type>",
required=True,
choices=['SCALE_OUT', 'SCALE_IN'],
help=_("SCALE_OUT or SCALE_IN for type of scale operation."))
scale_require_parameters.add_argument(
'--aspect-id',
required=True,
metavar="<aspect-id>",
help=_("Identifier of the scaling aspect."))
return parser
def args2body(self, parsed_args):
"""To store request body, call jsonfile2body.
Args:
parsed_args ([Namespace]): arguments of CLI.
Returns:
body ([dict]): Request body is stored
"""
body = {'type': parsed_args.type, 'aspectId': parsed_args.aspect_id}
if parsed_args.number_of_steps:
body['numberOfSteps'] = parsed_args.number_of_steps
if parsed_args.additional_param_file:
body.update(tacker_osc_utils.jsonfile2body(
parsed_args.additional_param_file))
return body
def take_action(self, parsed_args):
"""Execute scale_vnf_instance and output result comment.
Args:
parsed_args ([Namespace]): arguments of CLI.
"""
client = self.app.client_manager.tackerclient
result = client.scale_vnf_instance(
parsed_args.vnf_instance,
self.args2body(parsed_args))
if not result:
print((_('Scale request for VNF Instance %s has been accepted.')
% parsed_args.vnf_instance))
class ChangeExtConnVnfLcm(command.Command):
_description = _("Change External VNF Connectivity")
def get_parser(self, prog_name):
parser = super(ChangeExtConnVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to Change External VNF Connectivity"))
parser.add_argument(
'request_file',
metavar="<param-file>",
help=_("Specify change-ext-conn request parameters "
"in a json file."))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.change_ext_conn_vnf_instance(
parsed_args.vnf_instance, tacker_osc_utils.jsonfile2body(
parsed_args.request_file))
if not result:
print((_('Change External VNF Connectivity for VNF Instance %s '
'has been accepted.') % parsed_args.vnf_instance))
class ChangeVnfPkgVnfLcm(command.Command):
_description = _("Change Current VNF Package")
def get_parser(self, prog_name):
parser = super(ChangeVnfPkgVnfLcm, self).get_parser(prog_name)
parser.add_argument(
_VNF_INSTANCE,
metavar="<vnf-instance>",
help=_("VNF instance ID to Change Current VNF Package"))
parser.add_argument(
'request_file',
metavar="<param-file>",
help=_("Specify change-vnfpkg request parameters "
"in a json file."))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.change_vnfpkg_vnf_instance(
parsed_args.vnf_instance, tacker_osc_utils.jsonfile2body(
parsed_args.request_file))
if not result:
print((_('Change Current VNF Package for VNF Instance %s '
'has been accepted.') % parsed_args.vnf_instance))