Add heal vnf instance API

Implemented heal vnf instance API.
* POST /vnflcm/v1/vnf_instances/{vnf_instance_id}/heal}

Co-authored-By: Ajay Parja <ajay.parja@nttdata.com>
Co-authored-By: Nitin Uikey <nitin.uikey@nttdata.com>
Co-authored-By: Shubham Potale <shubham.potale@nttdata.com>

Change-Id: I38bdac64a8a3eb3f8880085b5a77db97e5a1a8f2
Blueprint: support-etsi-nfv-specs
This commit is contained in:
tpatil 2019-12-05 07:47:47 +00:00
parent fbb38266d6
commit b4f357f2b3
19 changed files with 1641 additions and 13 deletions

View File

@ -210,3 +210,19 @@ terminate = {
'required': ['terminationType'],
'additionalProperties': False,
}
heal = {
'type': 'object',
'properties': {
'cause': {'type': 'string', 'maxLength': 255},
'vnfcInstanceId': {
'type': 'array',
"items": {
"type": "string",
'format': 'uuid'
}
}
},
'additionalProperties': False,
}

View File

@ -269,8 +269,40 @@ class VnfLcmController(wsgi.Controller):
vnf_instance = self._get_vnf_instance(context, id)
self._terminate(context, vnf_instance, body)
@check_vnf_state(action="heal",
instantiation_state=[fields.VnfInstanceState.INSTANTIATED],
task_state=[None])
def _heal(self, context, vnf_instance, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
heal_vnf_request = objects.HealVnfRequest(context=context, **req_body)
inst_vnf_info = vnf_instance.instantiated_vnf_info
vnfc_resource_info_ids = [vnfc_resource_info.id for
vnfc_resource_info in inst_vnf_info.vnfc_resource_info]
for vnfc_id in heal_vnf_request.vnfc_instance_id:
# check if vnfc_id exists in vnfc_resource_info
if vnfc_id not in vnfc_resource_info_ids:
msg = _("Vnfc id %(vnfc_id)s not present in vnf instance "
"%(id)s")
raise webob.exc.HTTPBadRequest(
explanation=msg % {"vnfc_id": vnfc_id,
"id": vnf_instance.id})
vnf_instance.task_state = fields.VnfInstanceTaskState.HEALING
vnf_instance.save()
self.rpc_api.heal(context, vnf_instance, heal_vnf_request)
@wsgi.response(http_client.ACCEPTED)
@wsgi.expected_errors((http_client.BAD_REQUEST, http_client.FORBIDDEN,
http_client.NOT_FOUND, http_client.CONFLICT))
@validation.schema(vnf_lcm.heal)
def heal(self, request, id, body):
raise webob.exc.HTTPNotImplemented()
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'heal')
vnf_instance = self._get_vnf_instance(context, id)
self._heal(context, vnf_instance, body)
def create_resource():

View File

@ -70,6 +70,13 @@ class VnflcmAPIRouter(wsgi.Router):
"/vnf_instances/{id}/instantiate",
methods, controller, default_resource)
# Allowed methods on
# /vnflcm/v1/vnf_instances/{vnfInstanceId}/heal resource
methods = {"POST": "heal"}
self._setup_route(mapper,
"/vnf_instances/{id}/heal",
methods, controller, default_resource)
# Allowed methods on
# /vnflcm/v1/vnf_instances/{vnfInstanceId}/terminate resource
methods = {"POST": "terminate"}

View File

@ -266,6 +266,10 @@ class VnfPreInstantiationFailed(TackerException):
"%(error)s")
class VnfHealFailed(TackerException):
message = _("Heal Vnf failed for vnf %(id)s, error: %(error)s")
class OrphanedObjectError(TackerException):
msg_fmt = _('Cannot call %(method)s on orphaned %(objtype)s object')

View File

@ -438,6 +438,9 @@ class Conductor(manager.Manager):
LOG.error("Failed to update usage_state of vnf package %s",
vnf_package.id)
def heal(self, context, vnf_instance, heal_vnf_request):
self.vnflcm_driver.heal_vnf(context, vnf_instance, heal_vnf_request)
def init(args, **kwargs):
CONF(args=args, project='tacker',

View File

@ -49,3 +49,14 @@ class VNFLcmRPCAPI(object):
return rpc_method(context, 'terminate',
vnf_instance=vnf_instance,
terminate_vnf_req=terminate_vnf_req)
def heal(self, context, vnf_instance, heal_vnf_request, cast=True):
serializer = objects_base.TackerObjectSerializer()
client = rpc.get_client(self.target, version_cap=None,
serializer=serializer)
cctxt = client.prepare()
rpc_method = cctxt.cast if cast else cctxt.call
return rpc_method(context, 'heal',
vnf_instance=vnf_instance,
heal_vnf_request=heal_vnf_request)

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_utils import versionutils
from tacker.objects import base
from tacker.objects import fields
@ -33,10 +35,20 @@ class HealVnfAdditionalParams(base.TackerObject):
class HealVnfRequest(base.TackerObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added vnf_instance_id
VERSION = '1.1'
fields = {
'cause': fields.StringField(),
'vnfc_instance_id': fields.ListOfStringsField(nullable=True,
default=[]),
'cause': fields.StringField(nullable=True, default=None),
'additional_params': fields.ListOfObjectsField(
'HealVnfAdditionalParams')
'HealVnfAdditionalParams', default=[])
}
def obj_make_compatible(self, primitive, target_version):
super(HealVnfRequest, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 1) and 'vnfc_instance_id' in primitive:
del primitive['vnfc_instance_id']

View File

@ -22,6 +22,176 @@ from tacker.objects import fields
LOG = logging.getLogger(__name__)
def _get_vim_connection_info(vnf_instance):
vim_connection_infos = []
for vim_conn_info in vnf_instance.vim_connection_info:
new_vim_conn_info = vim_conn_info.obj_clone()
vim_connection_infos.append(new_vim_conn_info)
return vim_connection_infos
def _get_ext_managed_virtual_links(vnf_instantiated_info):
ext_managed_virtual_links = []
for ext_mng_vl_info in \
vnf_instantiated_info.ext_managed_virtual_link_info:
network_resource = ext_mng_vl_info.network_resource
ext_mng_vl_data = objects.ExtManagedVirtualLinkData(
id=ext_mng_vl_info.id,
vnf_virtual_link_desc_id=ext_mng_vl_info.vnf_virtual_link_desc_id,
resource_id=network_resource.resource_id)
ext_managed_virtual_links.append(ext_mng_vl_data)
return ext_managed_virtual_links
def _get_cp_instance_id(ext_virtual_link_info_id, vnf_instantiated_info):
cp_instances = []
for vnf_vl_res_info in \
vnf_instantiated_info.vnf_virtual_link_resource_info:
if ext_virtual_link_info_id == \
vnf_vl_res_info.vnf_virtual_link_desc_id:
for vnf_link_port in vnf_vl_res_info.vnf_link_ports:
cp_instances.append(vnf_link_port.cp_instance_id)
return cp_instances
def _get_cp_data_from_vnfc_resource_info(cp_instance_list,
vnf_instantiated_info):
vnfc_cp_infos = []
for vnfc_resource_info in vnf_instantiated_info.vnfc_resource_info:
for vnfc_cp_info in vnfc_resource_info.vnfc_cp_info:
if vnfc_cp_info.id in cp_instance_list:
vnfc_cp_infos.append(vnfc_cp_info)
return vnfc_cp_infos
def _get_link_ports(vnfc_cp_info_list, vnf_instantiated_info):
ext_link_ports = []
for vnfc_cp_info in vnfc_cp_info_list:
for ext_vl_info in vnf_instantiated_info.ext_virtual_link_info:
for ext_vl_port in ext_vl_info.ext_link_ports:
if ext_vl_port.id != vnfc_cp_info.vnf_ext_cp_id:
continue
resource_handle = ext_vl_port.resource_handle.obj_clone()
ext_link_port_data = objects.ExtLinkPortData(
id=ext_vl_port.id,
resource_handle=resource_handle)
ext_link_ports.append(ext_link_port_data)
break
return ext_link_ports
def _get_cp_protocol_data_list(ext_cp_info):
cp_protocol_data_list = []
def _get_ip_addresses(ip_addresses):
ip_addresses = []
for ip_address in ip_addresses:
# TODO(nitin-uikey): How to determine num_dynamic_addresses
# back from InstantiatedVnfInfo->IpAddress.
ip_address_data = IpAddress(
type=ip_address.type,
subnet_id=ip_address.subnet_id,
fixed_addresses=ip_address.addresses)
ip_addresses.append(ip_address_data)
return ip_addresses
for cp_protocol_info in ext_cp_info.cp_protocol_info:
if cp_protocol_info.ip_over_ethernet:
ip_addresses = _get_ip_addresses(cp_protocol_info.
ip_over_ethernet.ip_addresses)
ip_over_ethernet_data = objects.IpOverEthernetAddressData(
mac_address=cp_protocol_info.ip_over_ethernet.mac_address,
ip_addresses=ip_addresses)
else:
ip_over_ethernet_data = None
cp_protocol_data = objects.CpProtocolData(
layer_protocol=cp_protocol_info.layer_protocol,
ip_over_ethernet=ip_over_ethernet_data)
cp_protocol_data_list.append(cp_protocol_data)
return cp_protocol_data_list
def _get_cp_ids(vnfc_cp_info_list, vnf_instantiated_info):
ext_cps = []
for vnfc_cp_info in vnfc_cp_info_list:
for ext_cp_info in vnf_instantiated_info.ext_cp_info:
if ext_cp_info.cpd_id != vnfc_cp_info.cpd_id:
continue
ext_cp_configs = []
vnf_ext_cp_data = objects.VnfExtCpData()
vnf_ext_cp_data.cpd_id = ext_cp_info.cpd_id
cp_protocol_data_list = _get_cp_protocol_data_list(ext_cp_info)
# TODO(nitin-uikey) set cp_instance_id
# and prepare 1-N objects of VnfExtCpConfig
vnf_ext_cp_config = objects.VnfExtCpConfig(
link_port_id=vnfc_cp_info.vnf_ext_cp_id,
cp_protocol_data=cp_protocol_data_list)
ext_cp_configs.append(vnf_ext_cp_config)
vnf_ext_cp_data = objects.VnfExtCpData(
cpd_id=ext_cp_info.cpd_id,
cp_config=ext_cp_configs)
ext_cps.append(vnf_ext_cp_data)
break
return ext_cps
def _get_ext_virtual_link_data(vnf_instantiated_info):
ext_virtual_links = []
for ext_vl_info in \
vnf_instantiated_info.ext_virtual_link_info:
resource_handle = ext_vl_info.resource_handle
ext_vl_data = objects.ExtVirtualLinkData(
id=ext_vl_info.id,
resource_id=resource_handle.resource_id)
# call vnf virtual link resource info
cp_instances = _get_cp_instance_id(ext_vl_info.id,
vnf_instantiated_info)
# get cp info from vnfcresources
vnfc_cp_infos = _get_cp_data_from_vnfc_resource_info(cp_instances,
vnf_instantiated_info)
ext_vl_data.ext_link_ports = _get_link_ports(vnfc_cp_infos,
vnf_instantiated_info)
# assign the data to extcp info and link port
ext_vl_data.ext_cps = _get_cp_ids(vnfc_cp_infos,
vnf_instantiated_info)
ext_virtual_links.append(ext_vl_data)
return ext_virtual_links
@base.TackerObjectRegistry.register
class InstantiateVnfRequest(base.TackerObject):
# Version 1.0: Initial version
@ -87,6 +257,38 @@ class InstantiateVnfRequest(base.TackerObject):
ext_virtual_links=ext_virtual_links,
additional_params=additional_params)
@classmethod
def from_vnf_instance(cls, vnf_instance):
vnf_instantiated_info = vnf_instance.instantiated_vnf_info
# Vim connection info
vim_connection_info = _get_vim_connection_info(vnf_instance)
# Flavour id
flavour_id = vnf_instantiated_info.flavour_id
# Instantiation level
instantiation_level_id = vnf_instantiated_info.instantiation_level_id
# Externally managed virtual links
ext_managed_virtual_links = _get_ext_managed_virtual_links(
vnf_instantiated_info)
# External virtual links
ext_virtual_links = _get_ext_virtual_link_data(vnf_instantiated_info)
additional_params = vnf_instantiated_info.additional_params
instantiate_vnf_request = cls(flavour_id=flavour_id,
instantiation_level_id=instantiation_level_id,
ext_managed_virtual_links=ext_managed_virtual_links,
vim_connection_info=vim_connection_info,
ext_virtual_links=ext_virtual_links,
additional_params=additional_params)
return instantiate_vnf_request
@base.TackerObjectRegistry.register
class ExtManagedVirtualLinkData(base.TackerObject):

View File

@ -65,6 +65,17 @@ rules = [
'path': '/vnflcm/v1/vnf_instances/{vnfInstanceId}/terminate'
}
]
),
policy.DocumentedRuleDefault(
name=VNFLCM % 'heal',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Heal a VNF instance.",
operations=[
{
'method': 'POST',
'path': '/vnflcm/v1/vnf_instances/{vnfInstanceId}/heal'
}
]
)
]

View File

@ -342,6 +342,21 @@ class TestConductor(SqlTestCase):
mock_log.error.assert_called_once_with(expected_msg,
vnf_package_vnfd.package_uuid)
def test_heal_vnf_instance(self):
vnf_package_vnfd = self._create_and_upload_vnf_package()
vnf_instance_data = fake_obj.get_vnf_instance_data(
vnf_package_vnfd.vnfd_id)
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
vnf_instance.instantiation_state = \
fields.VnfInstanceState.INSTANTIATED
vnf_instance.save()
heal_vnf_req = objects.HealVnfRequest(cause="healing request")
self.conductor.heal(self.context, vnf_instance, heal_vnf_req)
self.vnflcm_driver.heal_vnf.assert_called_once_with(
self.context, mock.ANY, heal_vnf_req)
@mock.patch.object(os, 'remove')
@mock.patch.object(shutil, 'rmtree')
@mock.patch.object(os.path, 'exists')

View File

@ -0,0 +1,282 @@
# Copyright (c) 2020 NTT DATA
#
# 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.
from tacker import context
from tacker import objects
from tacker.objects import fields
from tacker.tests.unit import base
from tacker.tests import uuidsentinel
def get_instantiated_info_dict_for_ext_links_and_flavour_id():
return {"instantiated_vnf_info": {
"additional_params": {'key1': 'value1'},
"vnfc_resource_info": [{
"vdu_id": "VDU1",
"vnfc_cp_info": [{
"cp_protocol_info": [{
"ip_over_ethernet": {
"mac_address": "fa:16:3e:0d:6f:71"},
"layer_protocol": "IP_OVER_ETHERNET"}],
"vnf_ext_cp_id": None,
"cpd_id": "CP1",
"vnf_link_port_id": "a41417d8-5cba-4256-aa4a-c68b79bb2dc2",
"id": "350ab5d7-7cf0-4d24-aa2f-ec7e51149d56"}, {
"cp_protocol_info": [{
"layer_protocol": "IP_OVER_ETHERNET"}],
"vnf_ext_cp_id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe",
"cpd_id": "CP2",
"vnf_link_port_id": "b5f67448-87ee-4250-ad8b-514bc693f6c4",
"id": "2cab54a3-8ac5-4338-b586-b12e4ff1a89f"}, {
"vnf_link_port_id": "124f1923-9de1-4729-8d4e-bc0613066dc4",
"id": "b3341e7b-20bb-4ae5-8be9-b00cf79a9395",
"vnf_ext_cp_id": None,
"cpd_id": "CP3"}],
"compute_resource": {
"vim_level_resource_type": "OS::Nova::Server",
"resource_id": "c83ac219-376a-4016-b8b2-50c3707f71c4"},
"id": "0688be82-05a6-4b00-b869-d4719baf4f42",
"storage_resource_ids": [],
"metadata": {}}],
"ext_virtual_link_info": [{
"resource_handle": {
"vim_level_resource_type": None,
"resource_id": "7ec01a11-e584-404a-88bd-39a56b63e29c"},
"id": "net0"}, {
"resource_handle": {
"vim_level_resource_type": None,
"resource_id": "dc67ee99-e963-44e2-a152-f0fb492eae76"},
"id": "external_network",
"ext_link_ports": [{
"cp_instance_id": "f47a9e33-b31a-4290-828a-c7569c52bd0e",
"resource_handle": {
"vim_level_resource_type": "LINKPORT",
"resource_id": "67f7e772-0d31-4087-bf4c-2576fadcbdb7"},
"id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe"}]}],
"vnf_virtual_link_resource_info": [{
"vnf_link_ports": [{
"cp_instance_id": "b3341e7b-20bb-4ae5-8be9-b00cf79a9395",
"resource_handle": {
"vim_level_resource_type": "OS::Neutron::Port",
"resource_id": "87b567a0-783a-4c06-b0d8-cdafd40c51e3"},
"id": "124f1923-9de1-4729-8d4e-bc0613066dc4"}],
"network_resource": {
"vim_level_resource_type": "OS::Neutron::Net",
"resource_id": "413f4e46-21cf-41b1-be0f-de8d23f76cfd"},
"vnf_virtual_link_desc_id": "VL3",
"id": "84ffa547-1164-4e60-863d-c7dac770f824"}, {
"vnf_link_ports": [{
"cp_instance_id": "2cab54a3-8ac5-4338-b586-b12e4ff1a89f",
"resource_handle": {
"vim_level_resource_type": "OS::Neutron::Port",
"resource_id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe"},
"id": "b5f67448-87ee-4250-ad8b-514bc693f6c4"}],
"network_resource": {
"vim_level_resource_type": "OS::Neutron::Net",
"resource_id": "dc67ee99-e963-44e2-a152-f0fb492eae76"},
"vnf_virtual_link_desc_id": "external_network",
"id": "1050cce6-27ca-40df-b66e-160113fc4826"}, {
"vnf_link_ports": [{
"cp_instance_id": "350ab5d7-7cf0-4d24-aa2f-ec7e51149d56",
"resource_handle": {
"vim_level_resource_type": "OS::Neutron::Port",
"resource_id": "2bca5538-edd6-4590-ab1a-552ae7d0f81b"},
"id": "a41417d8-5cba-4256-aa4a-c68b79bb2dc2"}],
"network_resource": {
"vim_level_resource_type": "OS::Neutron::Net",
"resource_id": "7ec01a11-e584-404a-88bd-39a56b63e29c"},
"vnf_virtual_link_desc_id": "net0",
"id": "5ad98d4d-4b81-4b1b-a88b-1f64b66e151b"}],
"ext_managed_virtual_link_info": [{
"network_resource": {
"vim_level_resource_type": "OS::Neutron::Net",
"resource_id": "413f4e46-21cf-41b1-be0f-de8d23f76cfd"},
"id": "net1",
"vnf_link_ports": [{
"cp_instance_id": "b3341e7b-20bb-4ae5-8be9-b00cf79a9395",
"resource_handle": {
"vim_level_resource_type": "OS::Neutron::Port",
"resource_id": "87b567a0-783a-4c06-b0d8-cdafd40c51e3"},
"id": "124f1923-9de1-4729-8d4e-bc0613066dc4"}],
"vnf_virtual_link_desc_id": "VL3"}],
"flavour_id": "simple",
"vnf_state": "STARTED",
"ext_cp_info": [{
"cp_protocol_info": [{
"ip_over_ethernet": {
"mac_address": "fa:16:3e:0d:6f:71"},
"layer_protocol": "IP_OVER_ETHERNET"}],
"cpd_id": "CP1",
"id": "19f0aa71-9376-43dc-8e13-5e76e4bdc8bf",
"ext_link_port_id": None}, {
"cp_protocol_info": [{
"layer_protocol": "IP_OVER_ETHERNET"}],
"cpd_id": "CP2",
"id": "f47a9e33-b31a-4290-828a-c7569c52bd0e",
"ext_link_port_id": None}]
}
}
class InstantiateVnfRequestTestCase(base.TestCase):
def setUp(self):
super(InstantiateVnfRequestTestCase, self).setUp()
self.context = context.get_admin_context()
def _create_vim_connection_info(self, **kwargs):
vim_connection_info = objects.VimConnectionInfo(**kwargs)
return [vim_connection_info]
def _create_vnf_instance(self, vim_connection_info=None):
vnf_instance = objects.VnfInstance(
context=self.context,
vnf_instance_name="Sample vnf instance name",
vnf_instance_description="Sample vnf instance description",
vnfd_id=uuidsentinel.vnfd_id,
instantiation_state=fields.VnfInstanceState.INSTANTIATED,
vnf_provider='Company',
vnf_product_name='Sample VNF',
vnf_software_version='1.0',
vnfd_version='1.0',
tenant_id=uuidsentinel.tenant_id)
if vim_connection_info:
vnf_instance.vim_connection_info = vim_connection_info
return vnf_instance
def test_from_vnf_instance_with_flavour(self):
"""Map flavour id"""
inst_vnf_request = objects.InstantiateVnfRequest(flavour_id="simple")
instantiated_vnf_info = objects.InstantiatedVnfInfo(
flavour_id="simple")
vnf_instance = self._create_vnf_instance()
vnf_instance.instantiated_vnf_info = instantiated_vnf_info
inst_vnf_request_actual = objects.InstantiateVnfRequest.\
from_vnf_instance(vnf_instance)
self.compare_obj(inst_vnf_request, inst_vnf_request_actual)
def test_from_vnf_instance_with_flavour_and_instantiation_level(self):
"""Map flavour id and instantiation level"""
inst_vnf_request = objects.InstantiateVnfRequest(flavour_id="simple",
instantiation_level_id="instantiation_level_1")
instantiated_vnf_info = objects.InstantiatedVnfInfo(
flavour_id="simple",
instantiation_level_id="instantiation_level_1")
vnf_instance = self._create_vnf_instance()
vnf_instance.instantiated_vnf_info = instantiated_vnf_info
inst_vnf_request_actual = objects.InstantiateVnfRequest.\
from_vnf_instance(vnf_instance)
self.compare_obj(inst_vnf_request, inst_vnf_request_actual)
def test_from_vnf_instance_with_ext_vl_and_ext_managed_vl(self):
"""Map external and internal network information.
Map following information:
a) ext_virtual_link_info
b) ext_managed_virtual_link_info
"""
ext_vl_info = [{
"ext_cps": [{
"cp_config": [{
"cp_protocol_data": [{
"ip_over_ethernet": {
"mac_address": "fa:16:3e:0d:6f:71"},
"layer_protocol": "IP_OVER_ETHERNET"}]}],
"cpd_id": "CP1"}],
"id": "net0",
"resource_id": "7ec01a11-e584-404a-88bd-39a56b63e29c"}, {
"ext_cps": [{
"cp_config": [{
"cp_protocol_data": [{
"layer_protocol": "IP_OVER_ETHERNET"}],
"link_port_id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe"}],
"cpd_id": "CP2"}],
"ext_link_ports": [{
"id": "413f4e46-21cf-41b1-be0f-de8d23f76cfe",
"resource_handle": {
"resource_id": "67f7e772-0d31-4087-bf4c-2576fadcbdb7",
"vim_level_resource_type": "LINKPORT"}}],
"id": "external_network",
"resource_id": "dc67ee99-e963-44e2-a152-f0fb492eae76"}]
ext_mg_vl = {"id": "net1",
"resource_id": "413f4e46-21cf-41b1-be0f-de8d23f76cfd",
"vnf_virtual_link_desc_id": "VL3"}
vim_connection_info = [{
"id": "6b0ff598-60d6-49b4-a907-a1111de52d92",
"vim_id": uuidsentinel.vim_id,
"vim_type": "ETSINFV.OPENSTACK_KEYSTONE.v_2",
"access_info": {}}]
vnf_info_data = {'ext_managed_virtual_links': [ext_mg_vl],
'ext_virtual_links': ext_vl_info,
'vim_connection_info': vim_connection_info,
'flavour_id': 'simple',
'additional_params': {'key1': 'value1'}}
inst_vnf_request = objects.InstantiateVnfRequest.obj_from_primitive(
vnf_info_data, self.context)
response = get_instantiated_info_dict_for_ext_links_and_flavour_id()
instantiated_vnf_info_dict = response.get('instantiated_vnf_info')
instantiated_vnf_info = objects.InstantiatedVnfInfo.obj_from_primitive(
instantiated_vnf_info_dict, self.context)
vnf_instance = self._create_vnf_instance(
self._create_vim_connection_info(**vim_connection_info[0]))
vnf_instance.instantiated_vnf_info = instantiated_vnf_info
inst_vnf_req_actual = objects.InstantiateVnfRequest.from_vnf_instance(
vnf_instance)
self.assertEqual(inst_vnf_request.flavour_id,
inst_vnf_req_actual.flavour_id)
self.assertEqual(inst_vnf_request.instantiation_level_id,
inst_vnf_req_actual.instantiation_level_id)
self.assertEqual(inst_vnf_request.additional_params,
inst_vnf_req_actual.additional_params)
for expected, actual in zip(inst_vnf_request.ext_managed_virtual_links,
inst_vnf_req_actual.ext_managed_virtual_links):
self.compare_obj(expected, actual)
for expected, actual in zip(inst_vnf_request.ext_virtual_links,
inst_vnf_req_actual.ext_virtual_links):
self.assertEqual(expected.id, actual.id)
self.assertEqual(expected.resource_id, actual.resource_id)
for expected, actual in zip(inst_vnf_request.vim_connection_info,
inst_vnf_req_actual.vim_connection_info):
self.assertEqual(expected.id, actual.id)
self.assertEqual(expected.vim_id, actual.vim_id)
self.assertEqual(expected.vim_type, actual.vim_type)

View File

@ -835,3 +835,140 @@ class TestController(base.TestCase):
"terminate while the vnf instance is in this state.")
self.assertEqual(expected_msg % uuidsentinel.vnf_instance_id,
resp.json['conflictingRequest']['message'])
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(VNFLcmRPCAPI, "heal")
@ddt.data({'cause': 'healing'}, {})
def test_heal(self, body, mock_rpc_heal, mock_save,
mock_vnf_by_id):
vnf_instance_obj = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
mock_vnf_by_id.return_value = vnf_instance_obj
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/heal' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_rpc_heal.assert_called_once()
def test_heal_cause_max_length_exceeded(self):
body = {'cause': 'A' * 256}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/heal' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
@mock.patch.object(objects.VnfInstance, "get_by_id")
def test_heal_incorrect_instantiated_state(self, mock_vnf_by_id):
vnf_instance_obj = fakes.return_vnf_instance(
fields.VnfInstanceState.NOT_INSTANTIATED)
mock_vnf_by_id.return_value = vnf_instance_obj
body = {}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/heal' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.CONFLICT, resp.status_code)
expected_msg = ("Vnf instance %s in instantiation_state "
"NOT_INSTANTIATED. Cannot heal while the vnf instance "
"is in this state.")
self.assertEqual(expected_msg % uuidsentinel.vnf_instance_id,
resp.json['conflictingRequest']['message'])
@mock.patch.object(objects.VnfInstance, "get_by_id")
def test_heal_incorrect_task_state(self, mock_vnf_by_id):
vnf_instance_obj = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
task_state=fields.VnfInstanceTaskState.HEALING)
mock_vnf_by_id.return_value = vnf_instance_obj
body = {}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/heal' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.CONFLICT, resp.status_code)
expected_msg = ("Vnf instance %s in task_state "
"HEALING. Cannot heal while the vnf instance "
"is in this state.")
self.assertEqual(expected_msg % uuidsentinel.vnf_instance_id,
resp.json['conflictingRequest']['message'])
@mock.patch.object(objects.VnfInstance, "get_by_id")
def test_heal_with_invalid_vnfc_id(self, mock_vnf_by_id):
vnf_instance_obj = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
mock_vnf_by_id.return_value = vnf_instance_obj
body = {'vnfcInstanceId': [uuidsentinel.vnfc_instance_id]}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/heal' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
expected_msg = "Vnfc id %s not present in vnf instance %s"
self.assertEqual(expected_msg % (uuidsentinel.vnfc_instance_id,
uuidsentinel.vnf_instance_id), resp.json['badRequest']['message'])
@ddt.data('HEAD', 'PUT', 'DELETE', 'PATCH', 'GET')
def test_heal_invalid_http_method(self, method):
body = {}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/heal' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = method
resp = req.get_response(self.app)
self.assertEqual(http_client.METHOD_NOT_ALLOWED, resp.status_code)
@ddt.data({'attribute': 'cause', 'value': 123,
'expected_type': 'string'},
{'attribute': 'cause', 'value': True,
'expected_type': 'string'},
{'attribute': 'vnfcInstanceId', 'value': 123,
'expected_type': 'array'},
{'attribute': 'vnfcInstanceId', 'value': True,
'expected_type': 'array'},
)
@ddt.unpack
def test_heal_with_invalid_request_body(
self, attribute, value, expected_type):
body = {}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/29c770a3-02bc-4dfc-b4be-eb173ac00567/heal')
body.update({attribute: value})
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
exception = self.assertRaises(
exceptions.ValidationError, self.controller.heal,
req, body=body)
expected_message = \
("Invalid input for field/attribute {attribute}. Value: {value}. "
"{value} is not of type '{expected_type}'".
format(value=value, attribute=attribute,
expected_type=expected_type))
self.assertEqual(expected_message, exception.msg)

View File

@ -59,7 +59,7 @@ class FakeDriverManager(mock.Mock):
if self.fail_method_name and \
self.fail_method_name == 'create_wait':
raise InfraDriverException("create_wait failed")
if 'post_vnf_instantiation' in args:
elif 'post_vnf_instantiation' in args:
pass
if 'delete' in args:
if self.fail_method_name and \
@ -73,6 +73,18 @@ class FakeDriverManager(mock.Mock):
if self.fail_method_name and \
self.fail_method_name == 'delete_vnf_resource':
raise InfraDriverException("delete_vnf_resource failed")
elif 'heal_vnf' in args:
if self.fail_method_name and \
self.fail_method_name == 'heal_vnf':
raise InfraDriverException("heal_vnf failed")
elif 'heal_vnf_wait' in args:
if self.fail_method_name and \
self.fail_method_name == 'heal_vnf_wait':
raise InfraDriverException("heal_vnf_wait failed")
elif 'post_heal_vnf' in args:
if self.fail_method_name and \
self.fail_method_name == 'post_heal_vnf':
raise InfraDriverException("post_heal_vnf failed")
class FakeVimClient(mock.Mock):
@ -439,3 +451,259 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual("delete_vnf_resource failed", str(error))
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.VnfResource, "create")
@mock.patch.object(objects.VnfResource, "destroy")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_without_vnfc_instance(self, mock_log, mock_save,
mock_vnf_resource_list, mock_resource_destroy,
mock_resource_create, mock_vim, mock_vnf_package_vnfd):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
mock_vnf_resource_list.return_value = [fakes.return_vnf_resource()]
# Heal as per SOL003 i.e. without vnfcInstanceId
heal_vnf_req = objects.HealVnfRequest()
vim_obj = {'vim_id': uuidsentinel.vim_id,
'vim_name': 'fake_vim',
'vim_type': 'openstack',
'vim_auth': {
'auth_url': 'http://localhost/identity',
'password': 'test_pw',
'username': 'test_user',
'project_name': 'test_project'}}
mock_vim.return_value = vim_obj
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.heal_vnf(self.context, vnf_instance, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
# vnf resource software images will be deleted during
# deleting vnf instance.
self.assertEqual(1, mock_resource_destroy.call_count)
# Vnf resource software images will be created during
# instantiation.
self.assertEqual(1, mock_resource_create.call_count)
# Invoke will be called 7 times, 3 for deleting the vnf
# resources and 4 during instantiation.
self.assertEqual(7, self._vnf_manager.invoke.call_count)
expected_msg = ("Request received for healing vnf '%s' "
"is completed successfully")
mock_log.info.assert_called_with(expected_msg,
vnf_instance.id)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_without_vnfc_instance_infra_delete_fail(self, mock_log,
mock_save):
# Heal as per SOL003 i.e. without vnfcInstanceId
heal_vnf_req = objects.HealVnfRequest()
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
self._mock_vnf_manager(fail_method_name='delete')
driver = vnflcm_driver.VnfLcmDriver()
self.assertRaises(exceptions.VnfHealFailed,
driver.heal_vnf, self.context, vnf_instance, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
self.assertEqual(1, self._vnf_manager.invoke.call_count)
self.assertEqual(fields.VnfInstanceTaskState.ERROR,
vnf_instance.task_state)
expected_msg = ('Failed to delete vnf resources for vnf instance %s '
'before respawning. The vnf is in inconsistent '
'state. Error: delete failed')
mock_log.error.assert_called_with(expected_msg % vnf_instance.id)
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.VnfResource, "create")
@mock.patch.object(objects.VnfResource, "destroy")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_without_vnfc_instance_infra_instantiate_vnf_fail(self,
mock_log, mock_save, mock_vnf_resource_list,
mock_resource_destroy, mock_resource_create, mock_vim,
mock_vnf_package_vnfd):
vnf_package_vnfd = fakes.return_vnf_package_vnfd()
vnf_package_id = vnf_package_vnfd.package_uuid
mock_vnf_package_vnfd.return_value = vnf_package_vnfd
fake_csar = os.path.join(self.temp_dir, vnf_package_id)
cfg.CONF.set_override('vnf_package_csar_path', self.temp_dir,
group='vnf_package')
base_path = os.path.dirname(os.path.abspath(__file__))
sample_vnf_package_zip = os.path.join(
base_path, "../../etc/samples/sample_vnf_package_csar.zip")
extracted_zip_path = fake_csar
zipfile.ZipFile(sample_vnf_package_zip, 'r').extractall(
extracted_zip_path)
mock_vnf_resource_list.return_value = [fakes.return_vnf_resource()]
# Heal as per SOL003 i.e. without vnfcInstanceId
heal_vnf_req = objects.HealVnfRequest()
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
self._mock_vnf_manager(fail_method_name='instantiate_vnf')
driver = vnflcm_driver.VnfLcmDriver()
self.assertRaises(exceptions.VnfHealFailed,
driver.heal_vnf, self.context, vnf_instance, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
# vnf resource software images will be deleted during
# deleting vnf instance.
self.assertEqual(1, mock_resource_destroy.call_count)
# Vnf resource software images will be created during
# instantiation.
self.assertEqual(1, mock_resource_create.call_count)
self.assertEqual(5, self._vnf_manager.invoke.call_count)
self.assertEqual(fields.VnfInstanceTaskState.ERROR,
vnf_instance.task_state)
expected_msg = ('Failed to instantiate vnf instance %s '
'after termination. The vnf is in inconsistent '
'state. Error: Vnf instantiation failed for vnf %s, '
'error: instantiate_vnf failed')
mock_log.error.assert_called_with(expected_msg % (vnf_instance.id,
vnf_instance.id))
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_with_vnfc_instance(self, mock_log, mock_save):
heal_vnf_req = objects.HealVnfRequest(vnfc_instance_id=[
uuidsentinel.vnfc_instance_id_1])
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
task_state=fields.VnfInstanceTaskState.HEALING)
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.heal_vnf(self.context, vnf_instance, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
self.assertEqual(None, vnf_instance.task_state)
expected_msg = ("Request received for healing vnf '%s' "
"is completed successfully")
mock_log.info.assert_called_with(expected_msg,
vnf_instance.id)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_with_infra_heal_vnf_fail(self, mock_log, mock_save):
heal_vnf_req = objects.HealVnfRequest(vnfc_instance_id=[
uuidsentinel.vnfc_instance_id_1])
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
task_state=fields.VnfInstanceTaskState.HEALING)
self._mock_vnf_manager(fail_method_name='heal_vnf')
driver = vnflcm_driver.VnfLcmDriver()
self.assertRaises(exceptions.VnfHealFailed,
driver.heal_vnf, self.context, vnf_instance,
heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
self.assertEqual(1, self._vnf_manager.invoke.call_count)
self.assertEqual(fields.VnfInstanceTaskState.ERROR,
vnf_instance.task_state)
expected_msg = ("Failed to heal vnf %(id)s in infra driver. "
"Error: %(error)s")
mock_log.error.assert_called_with(expected_msg,
{'id': vnf_instance.id, 'error': 'heal_vnf failed'})
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_with_infra_heal_vnf_wait_fail(self, mock_log,
mock_save):
heal_vnf_req = objects.HealVnfRequest(vnfc_instance_id=[
uuidsentinel.vnfc_instance_id_1])
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
task_state=fields.VnfInstanceTaskState.HEALING)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
self._mock_vnf_manager(fail_method_name='heal_vnf_wait')
driver = vnflcm_driver.VnfLcmDriver()
# It won't raise any exception if infra driver raises
# heal_vnf_wait because there is a possibility the vnfc
# resources could go into inconsistent state so it would
# proceed further and call post_heal_vnf with a hope
# it will work and vnflcm can update the vnfc resources
# properly and hence the _vnf_manager.invoke.call_count
# should be 3 instead of 2.
driver.heal_vnf(self.context, vnf_instance, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
self.assertEqual(None, vnf_instance.task_state)
expected_msg = ('Failed to update vnf %(id)s resources for '
'instance%(instance)s. Error: %(error)s')
mock_log.error.assert_called_with(expected_msg,
{'id': vnf_instance.id,
'instance': vnf_instance.instantiated_vnf_info.instance_id,
'error': 'heal_vnf_wait failed'})
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch('tacker.vnflcm.vnflcm_driver.LOG')
def test_heal_vnf_with_infra_post_heal_vnf_fail(self, mock_log,
mock_save):
heal_vnf_req = objects.HealVnfRequest(vnfc_instance_id=[
uuidsentinel.vnfc_instance_id_1])
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
task_state=fields.VnfInstanceTaskState.HEALING)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
self._mock_vnf_manager(fail_method_name='post_heal_vnf')
driver = vnflcm_driver.VnfLcmDriver()
self.assertRaises(exceptions.VnfHealFailed,
driver.heal_vnf, self.context, vnf_instance, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
self.assertEqual(fields.VnfInstanceTaskState.ERROR,
vnf_instance.task_state)
expected_msg = ('Failed to store updated resources information for '
'instance %(instance)s for vnf %(id)s. '
'Error: %(error)s')
mock_log.error.assert_called_with(expected_msg,
{'instance': vnf_instance.instantiated_vnf_info.instance_id,
'id': vnf_instance.id,
'error': 'post_heal_vnf failed'})

View File

@ -22,6 +22,7 @@ import tempfile
from tacker.common import exceptions
from tacker import context
from tacker.extensions import vnfm
from tacker import objects
from tacker.tests.common import helpers
from tacker.tests.unit import base
from tacker.tests.unit.db import utils
@ -77,6 +78,39 @@ class TestOpenStack(base.FixturedTestCase):
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def _response_in_stack_get(self, id, stack_status='CREATE_COMPLETE'):
# response for heat_client's stack_get()
url = self.heat_url + '/stacks/' + id
json = {'stack': fd_utils.get_dummy_stack(status=stack_status)}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
def _response_in_stack_update(self, id):
# response for heat_client's stack_patch()
url = self.heat_url + '/stacks/' + id
self.requests_mock.register_uri('PATCH', url,
headers=self.json_headers)
def _response_resource_mark_unhealthy(self, id, resources,
raise_exception=False):
# response for heat_client's heatclient.resource_mark_unhealthy
if not resources:
return
class MyException(Exception):
pass
for resource in resources:
url = os.path.join(self.heat_url, 'stacks', id,
'myStack/60f83b5e/resources', resource['resource_name'])
if raise_exception:
self.requests_mock.register_uri('PATCH', url,
exc=MyException("Any stuff"))
else:
self.requests_mock.register_uri('PATCH', url)
def test_create_wait(self):
self._response_in_wait_until_stack_ready(["CREATE_IN_PROGRESS",
"CREATE_COMPLETE"])
@ -235,10 +269,16 @@ class TestOpenStack(base.FixturedTestCase):
region_name=None)
self.assertEqual(dummy_event['id'], event_id)
def _response_in_resource_get_list(self):
def _response_in_resource_get_list(self, stack_id=None,
resources=None):
# response for heat_client's resource_get_list()
url = self.heat_url + '/stacks/' + self.stack_id + '/resources'
json = {'resources': [fd_utils.get_dummy_resource()]}
if stack_id:
url = self.heat_url + '/stacks/' + stack_id + '/resources'
else:
url = self.heat_url + '/stacks/' + self.stack_id + '/resources'
resources = resources or [fd_utils.get_dummy_resource()]
json = {'resources': resources}
self.requests_mock.register_uri('GET', url, json=json,
headers=self.json_headers)
@ -763,3 +803,269 @@ class TestOpenStack(base.FixturedTestCase):
vnf_instance.instantiated_vnf_info.
ext_managed_virtual_link_info[0].vnf_link_ports[0].
resource_handle.resource_id)
def test_heal_vnf_instance(self):
v_s_resource_info = fd_utils.get_virtual_storage_resource_info(
desc_id="storage1")
storage_resource_ids = [v_s_resource_info.id]
vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF",
storage_resource_ids=storage_resource_ids)
inst_vnf_info = fd_utils.get_vnf_instantiated_info(
virtual_storage_resource_info=[v_s_resource_info],
vnfc_resource_info=[vnfc_resource_info])
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
heal_vnf_request = objects.HealVnfRequest(
vnfc_instance_id=[vnfc_resource_info.id],
cause="healing request")
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf.
resources = [{
'resource_name': vnfc_resource_info.vdu_id,
'resource_type': vnfc_resource_info.compute_resource.
vim_level_resource_type,
'physical_resource_id': vnfc_resource_info.compute_resource.
resource_id}, {
'resource_name': v_s_resource_info.virtual_storage_desc_id,
'resource_type': v_s_resource_info.storage_resource.
vim_level_resource_type,
'physical_resource_id': v_s_resource_info.storage_resource.
resource_id}]
self._response_in_stack_get(inst_vnf_info.instance_id)
self._response_in_resource_get_list(inst_vnf_info.instance_id,
resources=resources)
self._responses_in_stack_list(inst_vnf_info.instance_id,
resources=resources)
self._response_in_stack_update(inst_vnf_info.instance_id)
self._response_resource_mark_unhealthy(inst_vnf_info.instance_id,
resources=resources)
self.openstack.heal_vnf(
self.context, vnf_instance, vim_connection_info, heal_vnf_request)
history = self.requests_mock.request_history
patch_req = [req.url for req in history if req.method == 'PATCH']
# Total 3 times patch should be called, 2 for marking resources
# as unhealthy, and 1 for updating stack
self.assertEqual(3, len(patch_req))
def test_heal_vnf_instance_resource_mark_unhealthy_error(self):
vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF")
inst_vnf_info = fd_utils.get_vnf_instantiated_info(
vnfc_resource_info=[vnfc_resource_info])
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
heal_vnf_request = objects.HealVnfRequest(
vnfc_instance_id=[vnfc_resource_info.id],
cause="healing request")
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf.
resources = [{
'resource_name': vnfc_resource_info.vdu_id,
'resource_type': vnfc_resource_info.compute_resource.
vim_level_resource_type,
'physical_resource_id': vnfc_resource_info.compute_resource.
resource_id}]
self._response_in_stack_get(inst_vnf_info.instance_id)
self._response_in_resource_get_list(inst_vnf_info.instance_id,
resources=resources)
self._responses_in_stack_list(inst_vnf_info.instance_id,
resources=resources)
self._response_resource_mark_unhealthy(inst_vnf_info.instance_id,
resources=resources, raise_exception=True)
result = self.assertRaises(exceptions.VnfHealFailed,
self.openstack.heal_vnf, self.context, vnf_instance,
vim_connection_info, heal_vnf_request)
expected_msg = ("Failed to mark stack '%(id)s' resource as unhealthy "
"for resource '%(resource_name)s'") % {
'id': inst_vnf_info.instance_id,
'resource_name': resources[0]['resource_name']}
self.assertIn(expected_msg, str(result))
history = self.requests_mock.request_history
patch_req = [req.url for req in history if req.method == 'PATCH']
# only one time patch method be called for marking vdu resource
# as unhealthy
self.assertEqual(1, len(patch_req))
def test_heal_vnf_instance_incorrect_stack_status(self):
inst_vnf_info = fd_utils.get_vnf_instantiated_info()
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
heal_vnf_request = objects.HealVnfRequest(
vnfc_instance_id=[uuidsentinel.vnfc_resource_id],
cause="healing request")
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf.
self._response_in_stack_get(inst_vnf_info.instance_id,
stack_status='UPDATE_IN_PROGRESS')
result = self.assertRaises(exceptions.VnfHealFailed,
self.openstack.heal_vnf, self.context, vnf_instance,
vim_connection_info, heal_vnf_request)
expected_msg = ("Healing of vnf instance %s is possible only when "
"stack %s status is CREATE_COMPLETE,UPDATE_COMPLETE, "
"current stack status is UPDATE_IN_PROGRESS")
self.assertIn(expected_msg % (vnf_instance.id,
inst_vnf_info.instance_id), str(result))
def test_heal_vnf_wait(self):
inst_vnf_info = fd_utils.get_vnf_instantiated_info()
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf_wait.
self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS",
"UPDATE_COMPLETE"])
stack = self.openstack.heal_vnf_wait(
self.context, vnf_instance, vim_connection_info)
self.assertEqual('UPDATE_COMPLETE', stack.stack_status)
def test_heal_vnf_wait_fail(self):
inst_vnf_info = fd_utils.get_vnf_instantiated_info()
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf_wait.
self._response_in_wait_until_stack_ready(["UPDATE_IN_PROGRESS"])
self.openstack.STACK_RETRIES = 1
result = self.assertRaises(vnfm.VNFHealWaitFailed,
self.openstack.heal_vnf_wait, self.context, vnf_instance,
vim_connection_info)
expected_msg = ("VNF Heal action is not completed within 10 seconds "
"on stack %s") % inst_vnf_info.instance_id
self.assertIn(expected_msg, str(result))
def test_post_heal_vnf(self):
v_s_resource_info = fd_utils.get_virtual_storage_resource_info(
desc_id="storage1")
storage_resource_ids = [v_s_resource_info.id]
vnfc_resource_info = fd_utils.get_vnfc_resource_info(vdu_id="VDU_VNF",
storage_resource_ids=storage_resource_ids)
inst_vnf_info = fd_utils.get_vnf_instantiated_info(
virtual_storage_resource_info=[v_s_resource_info],
vnfc_resource_info=[vnfc_resource_info])
vnfc_resource_info.metadata['stack_id'] = inst_vnf_info.instance_id
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
heal_vnf_request = objects.HealVnfRequest(
vnfc_instance_id=[vnfc_resource_info.id],
cause="healing request")
# Change the physical_resource_id of both the resources, so
# that we can check it's updated in vnf instance after
# post_heal_vnf call.
resources = [{
'resource_name': vnfc_resource_info.vdu_id,
'resource_type': vnfc_resource_info.compute_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.compute_resource_id_new}, {
'resource_name': v_s_resource_info.virtual_storage_desc_id,
'resource_type': v_s_resource_info.storage_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.storage_resource_id_new}]
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf.
self._responses_in_stack_list(inst_vnf_info.instance_id,
resources=resources)
v_s_resource_info_old = v_s_resource_info.obj_clone()
vnfc_resource_info_old = vnfc_resource_info.obj_clone()
self.openstack.post_heal_vnf(self.context, vnf_instance,
vim_connection_info, heal_vnf_request)
vnfc_resource_info_new = vnf_instance.instantiated_vnf_info.\
vnfc_resource_info[0]
v_s_resource_info_new = vnf_instance.instantiated_vnf_info.\
virtual_storage_resource_info[0]
# Compare the resource_id, it should be updated with the new ones.
self.assertNotEqual(vnfc_resource_info_old.compute_resource.
resource_id, vnfc_resource_info_new.compute_resource.resource_id)
self.assertEqual(uuidsentinel.compute_resource_id_new,
vnfc_resource_info_new.compute_resource.resource_id)
self.assertNotEqual(v_s_resource_info_old.storage_resource.resource_id,
v_s_resource_info_new.storage_resource.resource_id)
self.assertEqual(uuidsentinel.storage_resource_id_new,
v_s_resource_info_new.storage_resource.resource_id)
def test_post_heal_vnf_fail(self):
vnfc_resource_info = fd_utils.get_vnfc_resource_info()
inst_vnf_info = fd_utils.get_vnf_instantiated_info(
vnfc_resource_info=[vnfc_resource_info])
vnfc_resource_info.metadata['stack_id'] = uuidsentinel.stack_id
vnf_instance = fd_utils.get_vnf_instance_object(
instantiated_vnf_info=inst_vnf_info)
vim_connection_info = fd_utils.get_vim_connection_info_object()
heal_vnf_request = objects.HealVnfRequest(
vnfc_instance_id=[vnfc_resource_info.id],
cause="healing request")
# Change the physical_resource_id of both the resources, so
# that we can check it's updated in vnf instance after
# post_heal_vnf call.
resources = [{
'resource_name': vnfc_resource_info.vdu_id,
'resource_type': vnfc_resource_info.compute_resource.
vim_level_resource_type,
'physical_resource_id': uuidsentinel.compute_resource_id_new}]
# Mock various heat APIs that will be called by heatclient
# during the process of heal_vnf.
self._responses_in_stack_list(inst_vnf_info.instance_id,
resources=resources)
result = self.assertRaises(exceptions.VnfHealFailed,
self.openstack.post_heal_vnf, self.context, vnf_instance,
vim_connection_info, heal_vnf_request)
expected_msg = ("Heal Vnf failed for vnf %s, error: Failed to find "
"stack_id %s") % (vnf_instance.id,
uuidsentinel.stack_id)
self.assertEqual(expected_msg, str(result))

View File

@ -264,8 +264,8 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
encodeutils.exception_to_unicode(exp))
def _delete_vnf_instance_resources(self, context, vnf_instance,
vim_connection_info,
terminate_vnf_req=None):
vim_connection_info, terminate_vnf_req=None,
update_instantiated_state=True):
if vnf_instance.instantiated_vnf_info and \
vnf_instance.instantiated_vnf_info.instance_id:
@ -285,9 +285,10 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
'delete', plugin=self, context=context,
vnf_id=instance_id, auth_attr=access_info)
vnf_instance.instantiation_state = \
fields.VnfInstanceState.NOT_INSTANTIATED
vnf_instance.save()
if update_instantiated_state:
vnf_instance.instantiation_state = \
fields.VnfInstanceState.NOT_INSTANTIATED
vnf_instance.save()
self._vnf_manager.invoke(vim_connection_info.vim_type,
'delete_wait', plugin=self, context=context,
@ -304,3 +305,114 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
vnf_resource=vnf_resource)
vnf_resource.destroy(context)
def _heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
inst_vnf_info = vnf_instance.instantiated_vnf_info
try:
self._vnf_manager.invoke(
vim_connection_info.vim_type, 'heal_vnf',
context=context, vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info,
heal_vnf_request=heal_vnf_request)
except Exception as exp:
with excutils.save_and_reraise_exception() as exc_ctxt:
exc_ctxt.reraise = False
LOG.error("Failed to heal vnf %(id)s in infra driver. "
"Error: %(error)s", {"id": vnf_instance.id, "error":
encodeutils.exception_to_unicode(exp)})
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
try:
self._vnf_manager.invoke(
vim_connection_info.vim_type, 'heal_vnf_wait',
context=context, vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info)
except Exception as exp:
LOG.error("Failed to update vnf %(id)s resources for instance"
"%(instance)s. Error: %(error)s",
{'id': vnf_instance.id, 'instance':
inst_vnf_info.instance_id, 'error':
encodeutils.exception_to_unicode(exp)})
try:
self._vnf_manager.invoke(
vim_connection_info.vim_type, 'post_heal_vnf',
context=context, vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info,
heal_vnf_request=heal_vnf_request)
self._vnf_instance_update(context, vnf_instance, task_state=None)
except Exception as exp:
with excutils.save_and_reraise_exception() as exc_ctxt:
exc_ctxt.reraise = False
LOG.error("Failed to store updated resources information for "
"instance %(instance)s for vnf %(id)s. "
"Error: %(error)s",
{'id': vnf_instance.id, 'instance':
inst_vnf_info.instance_id, 'error':
encodeutils.exception_to_unicode(exp)})
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exp))
def _respawn_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
try:
self._delete_vnf_instance_resources(context, vnf_instance,
vim_connection_info, update_instantiated_state=False)
except Exception as exc:
with excutils.save_and_reraise_exception() as exc_ctxt:
exc_ctxt.reraise = False
err_msg = ("Failed to delete vnf resources for vnf instance "
"%(id)s before respawning. The vnf is in "
"inconsistent state. Error: %(error)s")
LOG.error(err_msg % {"id": vnf_instance.id,
"error": six.text_type(exc)})
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exc))
# InstantiateVnfRequest is not stored in the db as it's mapped
# to InstantiatedVnfInfo version object. Convert InstantiatedVnfInfo
# version object to InstantiateVnfRequest so that vnf can be
# instantiated.
instantiate_vnf_request = objects.InstantiateVnfRequest.\
from_vnf_instance(vnf_instance)
try:
self._instantiate_vnf(context, vnf_instance, vim_connection_info,
instantiate_vnf_request)
except Exception as exc:
with excutils.save_and_reraise_exception() as exc_ctxt:
exc_ctxt.reraise = False
err_msg = ("Failed to instantiate vnf instance "
"%(id)s after termination. The vnf is in "
"inconsistent state. Error: %(error)s")
LOG.error(err_msg % {"id": vnf_instance.id,
"error": six.text_type(exc)})
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=encodeutils.exception_to_unicode(exc))
self._vnf_instance_update(context, vnf_instance,
instantiation_state=fields.VnfInstanceState.INSTANTIATED,
task_state=None)
@log.log
@revert_to_error_task_state
def heal_vnf(self, context, vnf_instance, heal_vnf_request):
LOG.info("Request received for healing vnf '%s'", vnf_instance.id)
vim_info = vnflcm_utils._get_vim(context,
vnf_instance.vim_connection_info)
vim_connection_info = objects.VimConnectionInfo.obj_from_primitive(
vim_info, context)
if not heal_vnf_request.vnfc_instance_id:
self._respawn_vnf(context, vnf_instance, vim_connection_info,
heal_vnf_request)
else:
self._heal_vnf(context, vnf_instance, vim_connection_info,
heal_vnf_request)
LOG.info("Request received for healing vnf '%s' is completed "
"successfully", vnf_instance.id)

View File

@ -102,3 +102,34 @@ class VnfAbstractDriver(extensions.PluginInterface):
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
pass
@abc.abstractmethod
def heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
"""Heal vnf
:param context: A RequestContext
:param vnf_instance: tacker.objects.VnfInstance to be healed
:vim_info: Credentials to initialize Vim connection
:heal_vnf_request: tacker.objects.HealVnfRequest object containing
parameters passed in the heal request
"""
pass
@abc.abstractmethod
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
"""Check vnf is healed successfully"""
pass
@abc.abstractmethod
def post_heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
"""Update resource_id for each vnfc resources
:param context: A RequestContext
:param vnf_instance: tacker.objects.VnfInstance to be healed
:vim_info: Credentials to initialize Vim connection
:heal_vnf_request: tacker.objects.HealVnfRequest object containing
parameters passed in the heal request
"""
pass

View File

@ -567,3 +567,14 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
raise NotImplementedError()
def heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
raise NotImplementedError()
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
raise NotImplementedError()
def post_heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
raise NotImplementedError()

View File

@ -93,3 +93,14 @@ class VnfNoop(abstract_driver.VnfAbstractDriver):
def post_vnf_instantiation(self, context, vnf_instance,
vim_connection_info):
pass
def heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
pass
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
pass
def post_heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
pass

View File

@ -796,3 +796,160 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
resource_details[id].update({'child_stack': child_stack})
return resource_details
def _get_vnfc_resources_from_heal_request(self, inst_vnf_info,
heal_vnf_request):
if not heal_vnf_request.vnfc_instance_id:
# include all vnfc resources
return [resource for resource in inst_vnf_info.vnfc_resource_info]
vnfc_resources = []
for vnfc_resource in inst_vnf_info.vnfc_resource_info:
if vnfc_resource.id in heal_vnf_request.vnfc_instance_id:
vnfc_resources.append(vnfc_resource)
return vnfc_resources
@log.log
def heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
inst_vnf_info = vnf_instance.instantiated_vnf_info
heatclient = hc.HeatClient(access_info, region_name=region_name)
def _get_storage_resources(vnfc_resource):
# Prepare list of storage resources to be marked unhealthy
for storage_id in vnfc_resource.storage_resource_ids:
for storage_res_info in inst_vnf_info. \
virtual_storage_resource_info:
if storage_res_info.id == storage_id:
yield storage_res_info.virtual_storage_desc_id, \
storage_res_info.storage_resource.resource_id
def _get_vdu_resources(vnfc_resources):
# Prepare list of vdu resources to be marked unhealthy
vdu_resources = []
for vnfc_resource in vnfc_resources:
resource_details = {"resource_name": vnfc_resource.vdu_id,
"physical_resource_id":
vnfc_resource.compute_resource.resource_id}
vdu_resources.append(resource_details)
# Get storage resources
for resource_name, resource_id in \
_get_storage_resources(vnfc_resource):
resource_details = {"resource_name": resource_name,
"physical_resource_id": resource_id}
vdu_resources.append(resource_details)
return vdu_resources
def _prepare_stack_resources_for_updation(vdu_resources,
stack_resources):
for resource in vdu_resources:
for stack_uuid, resources in stack_resources.items():
res_details = resources.get(resource['resource_name'])
if res_details and res_details['physical_resource_id'] == \
resource['physical_resource_id']:
yield stack_uuid, resource['resource_name']
def _resource_mark_unhealthy():
vnfc_resources = self._get_vnfc_resources_from_heal_request(
inst_vnf_info, heal_vnf_request)
vdu_resources = _get_vdu_resources(vnfc_resources)
stack_resources = self._get_stack_resources(
inst_vnf_info.instance_id, heatclient)
cause = heal_vnf_request.cause or "Healing"
for stack_uuid, resource_name in \
_prepare_stack_resources_for_updation(
vdu_resources, stack_resources):
try:
LOG.info("Marking resource %(resource)s as unhealthy for "
"stack %(stack)s for vnf instance %(id)s",
{"resource": resource_name,
"stack": stack_uuid,
"id": vnf_instance.id})
heatclient.resource_mark_unhealthy(
stack_id=stack_uuid,
resource_name=resource_name, mark_unhealthy=True,
resource_status_reason=cause)
except Exception as exp:
msg = ("Failed to mark stack '%(stack_id)s' resource as "
"unhealthy for resource '%(resource)s', "
"Error: %(error)s")
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=msg % {"stack_id": inst_vnf_info.instance_id,
"resource": resource_name,
"error": str(exp)})
def _get_stack_status():
stack_statuses = ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
stack = heatclient.get(inst_vnf_info.instance_id)
if stack.stack_status not in stack_statuses:
error = ("Healing of vnf instance %(id)s is possible only "
"when stack %(stack_id)s status is %(statuses)s, "
"current stack status is %(status)s")
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=error % {"id": vnf_instance.id,
"stack_id": inst_vnf_info.instance_id,
"statuses": ",".join(stack_statuses),
"status": stack.stack_status})
_get_stack_status()
_resource_mark_unhealthy()
LOG.info("Updating stack %(stack)s for vnf instance %(id)s",
{"stack": inst_vnf_info.instance_id, "id": vnf_instance.id})
heatclient.update(stack_id=inst_vnf_info.instance_id, existing=True)
@log.log
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
"""Check vnf is healed successfully"""
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
inst_vnf_info = vnf_instance.instantiated_vnf_info
stack = self._wait_until_stack_ready(inst_vnf_info.instance_id,
access_info, infra_cnst.STACK_UPDATE_IN_PROGRESS,
infra_cnst.STACK_UPDATE_COMPLETE, vnfm.VNFHealWaitFailed,
region_name=region_name)
return stack
def post_heal_vnf(self, context, vnf_instance, vim_connection_info,
heal_vnf_request):
"""Update resource_id for each vnfc resources
:param context: A RequestContext
:param vnf_instance: tacker.objects.VnfInstance to be healed
:vim_info: Credentials to initialize Vim connection
:heal_vnf_request: tacker.objects.HealVnfRequest object containing
parameters passed in the heal request
"""
access_info = vim_connection_info.access_info
region_name = access_info.get('region')
heatclient = hc.HeatClient(access_info, region_name)
inst_vnf_info = vnf_instance.instantiated_vnf_info
stack_resources = self._get_stack_resources(inst_vnf_info.instance_id,
heatclient)
vnfc_resources = self._get_vnfc_resources_from_heal_request(
inst_vnf_info, heal_vnf_request)
for vnfc_res_info in vnfc_resources:
stack_id = vnfc_res_info.metadata.get("stack_id")
resources = stack_resources.get(stack_id)
if not resources:
# NOTE(tpatil): This could happen when heat child stacks
# and the stack_id stored in metadata of vnfc_res_info are
# not in sync. There is no point in syncing inconsistent
# resources information so exit with an error,
error = "Failed to find stack_id %s" % stack_id
raise exceptions.VnfHealFailed(id=vnf_instance.id,
error=error)
self._update_vnfc_resource_info(vnf_instance, vnfc_res_info,
{stack_id: resources}, update_network_resource=False)