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:
parent
fbb38266d6
commit
b4f357f2b3
@ -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,
|
||||
}
|
||||
|
@ -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():
|
||||
|
@ -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"}
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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']
|
||||
|
@ -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):
|
||||
|
@ -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'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
@ -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')
|
||||
|
282
tacker/tests/unit/objects/test_instantiate_vnf_req.py
Normal file
282
tacker/tests/unit/objects/test_instantiate_vnf_req.py
Normal 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)
|
@ -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)
|
||||
|
@ -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'})
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user