Add terminate vnf instance API

Implemented terminate vnf instance API.

* GET /vnflcm/v1/vnf_instances/{vnf_instance_id}/terminate

Co-Authored-By: tpatil <tushar.vitthal.patil@gmail.com>
Co-Authored-By: Shubham Potale <shubham.potale@nttdata.com>
Co-Authored-By: Sameer Thakur <sameer.thakur@nttdata.com>
Change-Id: I69b7ef12038aa410db7e50671df2931beb761223
Blueprint: support-etsi-nfv-specs
This commit is contained in:
Ajay Parja 2019-12-05 07:47:47 +00:00 committed by tpatil
parent 19ff0032f0
commit fbb38266d6
15 changed files with 704 additions and 8 deletions

View File

@ -199,3 +199,14 @@ instantiate = {
'required': ['flavourId'],
'additionalProperties': False,
}
terminate = {
'type': 'object',
'properties': {
'terminationType': {'type': 'string',
'enum': ['FORCEFUL', 'GRACEFUL']},
'gracefulTerminationTimeout': {'type': 'integer', 'minimum': 0}
},
'required': ['terminationType'],
'additionalProperties': False,
}

View File

@ -245,8 +245,29 @@ class VnfLcmController(wsgi.Controller):
self._instantiate(context, vnf_instance, body)
@check_vnf_state(action="terminate",
instantiation_state=[fields.VnfInstanceState.INSTANTIATED],
task_state=[None])
def _terminate(self, context, vnf_instance, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body)
terminate_vnf_req = \
objects.TerminateVnfRequest.obj_from_primitive(
req_body, context=context)
vnf_instance.task_state = fields.VnfInstanceTaskState.TERMINATING
vnf_instance.save()
self.rpc_api.terminate(context, vnf_instance, terminate_vnf_req)
@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.terminate)
def terminate(self, request, id, body):
raise webob.exc.HTTPNotImplemented()
context = request.environ['tacker.context']
context.can(vnf_lcm_policies.VNFLCM % 'terminate')
vnf_instance = self._get_vnf_instance(context, id)
self._terminate(context, vnf_instance, body)
def heal(self, request, id, body):
raise webob.exc.HTTPNotImplemented()

View File

@ -424,6 +424,20 @@ class Conductor(manager.Manager):
vnf_package.save()
def terminate(self, context, vnf_instance, terminate_vnf_req):
self.vnflcm_driver.terminate_vnf(context, vnf_instance,
terminate_vnf_req)
vnf_package_vnfd = objects.VnfPackageVnfd.get_by_id(context,
vnf_instance.vnfd_id)
vnf_package = objects.VnfPackage.get_by_id(context,
vnf_package_vnfd.package_uuid, expected_attrs=['vnfd'])
try:
self._update_package_usage_state(context, vnf_package)
except Exception:
LOG.error("Failed to update usage_state of vnf package %s",
vnf_package.id)
def init(args, **kwargs):
CONF(args=args, project='tacker',

View File

@ -38,3 +38,14 @@ class VNFLcmRPCAPI(object):
return rpc_method(context, 'instantiate',
vnf_instance=vnf_instance,
instantiate_vnf=instantiate_vnf)
def terminate(self, context, vnf_instance, terminate_vnf_req, 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, 'terminate',
vnf_instance=vnf_instance,
terminate_vnf_req=terminate_vnf_req)

View File

@ -34,3 +34,4 @@ def register_all():
__import__('tacker.objects.vim_connection')
__import__('tacker.objects.instantiate_vnf_req')
__import__('tacker.objects.vnf_resources')
__import__('tacker.objects.terminate_vnf_req')

View File

@ -166,3 +166,14 @@ class IpAddressType(BaseTackerEnum):
class IpAddressTypeField(BaseEnumField):
AUTO_TYPE = IpAddressType()
class VnfInstanceTerminationType(BaseTackerEnum):
FORCEFUL = 'FORCEFUL'
GRACEFUL = 'GRACEFUL'
ALL = (FORCEFUL, GRACEFUL)
class VnfInstanceTerminationTypeField(BaseEnumField):
AUTO_TYPE = VnfInstanceTerminationType()

View File

@ -0,0 +1,54 @@
# Copyright (C) 2020 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from tacker.objects import base
from tacker.objects import fields
LOG = logging.getLogger(__name__)
@base.TackerObjectRegistry.register
class TerminateVnfRequest(base.TackerObject, base.TackerPersistentObject):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'termination_type': fields.VnfInstanceTerminationTypeField(
nullable=False),
'graceful_termination_timeout': fields.IntegerField(nullable=True,
default=0)
}
@classmethod
def obj_from_primitive(cls, primitive, context):
if 'tacker_object.name' in primitive:
obj_terminate_vnf_req = super(
TerminateVnfRequest, cls).obj_from_primitive(
primitive, context)
else:
obj_terminate_vnf_req = TerminateVnfRequest._from_dict(primitive)
return obj_terminate_vnf_req
@classmethod
def _from_dict(cls, data_dict):
termination_type = data_dict.get('termination_type')
graceful_termination_timeout = \
data_dict.get('graceful_termination_timeout', 0)
return cls(termination_type=termination_type,
graceful_termination_timeout=graceful_termination_timeout)

View File

@ -293,6 +293,17 @@ class InstantiatedVnfInfo(base.TackerObject, base.TackerObjectDictCompat,
return data
def reinitialize(self):
# Reinitialize vnf to non instantiated state.
self.ext_cp_info = []
self.ext_virtual_link_info = []
self.ext_managed_virtual_link_info = []
self.vnfc_resource_info = []
self.vnf_virtual_link_resource_info = []
self.virtual_storage_resource_info = []
self.instance_id = None
self.vnf_state = fields.VnfOperationalStateType.STOPPED
@base.TackerObjectRegistry.register
class VnfExtCpInfo(base.TackerObject, base.TackerObjectDictCompat,

View File

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

View File

@ -29,7 +29,7 @@ import tacker.conf
from tacker import context
from tacker.glance_store import store as glance_store
from tacker import objects
from tacker.objects import vnf_package
from tacker.objects import fields
from tacker.tests.unit.conductor import fakes
from tacker.tests.unit.db.base import SqlTestCase
from tacker.tests.unit.objects import fakes as fake_obj
@ -73,7 +73,7 @@ class TestConductor(SqlTestCase):
'tacker.vnflcm.vnflcm_driver.VnfLcmDriver', fake_vnflcm_driver)
def _create_vnf_package(self):
vnfpkgm = vnf_package.VnfPackage(context=self.context,
vnfpkgm = objects.VnfPackage(context=self.context,
**fakes.VNF_PACKAGE_DATA)
vnfpkgm.create()
return vnfpkgm
@ -251,10 +251,101 @@ class TestConductor(SqlTestCase):
mock_log.error.assert_called_once_with(expected_log,
vnf_package_vnfd.package_uuid)
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
def test_terminate_vnf_instance(self, mock_package_in_use):
vnf_package_vnfd = self._create_and_upload_vnf_package()
vnf_instance_data = fake_obj.get_vnf_instance_data(
vnf_package_vnfd.vnfd_id)
mock_package_in_use.return_value = True
vnf_instance_data['instantiation_state'] =\
fields.VnfInstanceState.INSTANTIATED
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.GRACEFUL)
self.conductor.terminate(self.context, vnf_instance,
terminate_vnf_req)
self.vnflcm_driver.terminate_vnf.assert_called_once_with(
self.context, vnf_instance, terminate_vnf_req)
mock_package_in_use.assert_called_once()
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
def test_terminate_vnf_instance_with_usage_state_not_in_use(self,
mock_vnf_package_is_package_in_use):
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_data['instantiation_state'] =\
fields.VnfInstanceState.INSTANTIATED
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
mock_vnf_package_is_package_in_use.return_value = False
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.GRACEFUL)
self.conductor.terminate(self.context, vnf_instance,
terminate_vnf_req)
self.vnflcm_driver.terminate_vnf.assert_called_once_with(
self.context, vnf_instance, terminate_vnf_req)
mock_vnf_package_is_package_in_use.assert_called_once()
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
def test_terminate_vnf_instance_with_usage_state_already_in_use(self,
mock_vnf_package_is_package_in_use):
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_data['instantiation_state'] =\
fields.VnfInstanceState.INSTANTIATED
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
mock_vnf_package_is_package_in_use.return_value = True
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.GRACEFUL)
self.conductor.terminate(self.context, vnf_instance,
terminate_vnf_req)
self.vnflcm_driver.terminate_vnf.assert_called_once_with(
self.context, vnf_instance, terminate_vnf_req)
mock_vnf_package_is_package_in_use.assert_called_once()
@mock.patch.object(objects.VnfPackage, 'is_package_in_use')
@mock.patch('tacker.conductor.conductor_server.LOG')
def test_terminate_vnf_instance_failed_to_update_usage_state(
self, mock_log, mock_is_package_in_use):
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_data['instantiation_state'] =\
fields.VnfInstanceState.INSTANTIATED
vnf_instance = objects.VnfInstance(context=self.context,
**vnf_instance_data)
vnf_instance.create()
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.GRACEFUL)
mock_is_package_in_use.side_effect = Exception
self.conductor.terminate(self.context, vnf_instance,
terminate_vnf_req)
self.vnflcm_driver.terminate_vnf.assert_called_once_with(
self.context, vnf_instance, terminate_vnf_req)
expected_msg = "Failed to update usage_state of vnf package %s"
mock_log.error.assert_called_once_with(expected_msg,
vnf_package_vnfd.package_uuid)
@mock.patch.object(os, 'remove')
@mock.patch.object(shutil, 'rmtree')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(vnf_package.VnfPackagesList, 'get_by_filters')
@mock.patch.object(objects.VnfPackagesList, 'get_by_filters')
def test_run_cleanup_vnf_packages(self, mock_get_by_filter,
mock_exists, mock_rmtree,
mock_remove):

View File

@ -0,0 +1,51 @@
# Copyright (C) 2020 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tacker import context
from tacker import objects
from tacker.tests.unit.db.base import SqlTestCase
class TestTerminateVnfRequest(SqlTestCase):
def setUp(self):
super(TestTerminateVnfRequest, self).setUp()
self.context = context.get_admin_context()
def _get_terminate_vnf_request(self):
terminate_vnf_request = {
'termination_type': 'GRACEFUL',
'graceful_termination_timeout': 10
}
return terminate_vnf_request
def test_obj_from_primitive(self):
terminate_vnf_request = self._get_terminate_vnf_request()
result = objects.TerminateVnfRequest.obj_from_primitive(
terminate_vnf_request, self.context)
self.assertTrue(isinstance(result, objects.TerminateVnfRequest))
self.assertEqual('GRACEFUL', result.termination_type)
self.assertEqual(terminate_vnf_request['graceful_termination_timeout'],
result.graceful_termination_timeout)
def test_obj_from_primitive_without_timeout(self):
terminate_vnf_request = self._get_terminate_vnf_request()
terminate_vnf_request.pop('graceful_termination_timeout')
result = objects.TerminateVnfRequest.obj_from_primitive(
terminate_vnf_request, self.context)
self.assertTrue(isinstance(result, objects.TerminateVnfRequest))
self.assertEqual('GRACEFUL', result.termination_type)
self.assertEqual(0, result.graceful_termination_timeout)

View File

@ -695,3 +695,143 @@ class TestController(base.TestCase):
resp = req.get_response(self.app)
self.assertEqual(http_client.METHOD_NOT_ALLOWED, resp.status_code)
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(VNFLcmRPCAPI, "terminate")
@ddt.data({'terminationType': 'FORCEFUL'},
{'terminationType': 'GRACEFUL'},
{'terminationType': 'GRACEFUL',
'gracefulTerminationTimeout': 10})
def test_terminate(self, body, mock_terminate, mock_save, mock_get_by_id):
vnf_instance_obj = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
mock_get_by_id.return_value = vnf_instance_obj
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % 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_terminate.assert_called_once()
@ddt.data(
{'attribute': 'terminationType', 'value': "TEST",
'expected_type': 'enum'},
{'attribute': 'terminationType', 'value': 123,
'expected_type': 'enum'},
{'attribute': 'terminationType', 'value': True,
'expected_type': 'enum'},
{'attribute': 'gracefulTerminationTimeout', 'value': True,
'expected_type': 'integer'},
{'attribute': 'gracefulTerminationTimeout', 'value': "test",
'expected_type': 'integer'}
)
@ddt.unpack
def test_terminate_with_invalid_request_body(
self, attribute, value, expected_type):
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % uuidsentinel.vnf_instance_id)
body = {'terminationType': 'GRACEFUL',
'gracefulTerminationTimeout': 10}
body.update({attribute: value})
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
expected_message = ("Invalid input for field/attribute {attribute}. "
"Value: {value}.".format(value=value, attribute=attribute))
exception = self.assertRaises(exceptions.ValidationError,
self.controller.terminate,
req, constants.UUID, body=body)
self.assertIn(expected_message, exception.msg)
def test_terminate_missing_termination_type(self):
body = {'gracefulTerminationTimeout': 10}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % uuidsentinel.vnf_instance_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
# Call terminate API
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
self.assertEqual("'terminationType' is a required property",
resp.json['badRequest']['message'])
@ddt.data('GET', 'HEAD', 'PUT', 'DELETE', 'PATCH')
def test_terminate_invalid_http_method(self, method):
# Wrong HTTP method
body = {'terminationType': 'GRACEFUL',
'gracefulTerminationTimeout': 10}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % 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)
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
def test_terminate_non_existing_vnf_instance(self, mock_vnf_by_id):
body = {'terminationType': 'GRACEFUL',
'gracefulTerminationTimeout': 10}
mock_vnf_by_id.side_effect = exceptions.VnfInstanceNotFound
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % 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.NOT_FOUND, resp.status_code)
self.assertEqual("Can not find requested vnf instance: %s" %
uuidsentinel.vnf_instance_id,
resp.json['itemNotFound']['message'])
@mock.patch.object(objects.vnf_instance, "_vnf_instance_get_by_id")
def test_terminate_incorrect_instantiation_state(self, mock_vnf_by_id):
mock_vnf_by_id.return_value = fakes.return_vnf_instance()
body = {"terminationType": "FORCEFUL"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % 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 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")
def test_terminate_incorrect_task_state(self, mock_vnf_by_id):
vnf_instance = fakes.return_vnf_instance(
instantiated_state=fields.VnfInstanceState.INSTANTIATED,
task_state=fields.VnfInstanceTaskState.TERMINATING)
mock_vnf_by_id.return_value = vnf_instance
body = {"terminationType": "FORCEFUL"}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/terminate' % 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 TERMINATING. Cannot "
"terminate while the vnf instance is in this state.")
self.assertEqual(expected_msg % uuidsentinel.vnf_instance_id,
resp.json['conflictingRequest']['message'])

View File

@ -23,10 +23,12 @@ from tacker.common import exceptions
from tacker.common import utils
from tacker import context
from tacker import objects
from tacker.objects import fields
from tacker.tests.unit.db import base as db_base
from tacker.tests.unit.vnflcm import fakes
from tacker.tests import uuidsentinel
from tacker.vnflcm import vnflcm_driver
from tacker.vnfm import vim_client
class InfraDriverException(Exception):
@ -256,7 +258,7 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(expected_error % vnf_instance_obj.id, str(error))
self.assertEqual("NOT_INSTANTIATED",
vnf_instance_obj.instantiation_state)
self.assertEqual(1, mock_vnf_instance_save.call_count)
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(2, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@ -298,8 +300,8 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(expected_error % vnf_instance_obj.id, str(error))
self.assertEqual("NOT_INSTANTIATED",
vnf_instance_obj.instantiation_state)
self.assertEqual(1, mock_vnf_instance_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
self.assertEqual(3, mock_vnf_instance_save.call_count)
self.assertEqual(5, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar)
@ -334,3 +336,106 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(2, mock_create.call_count)
self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state)
shutil.rmtree(fake_csar)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
@mock.patch.object(objects.VnfResource, "destroy")
def test_terminate_vnf(self, mock_resource_destroy, mock_resource_list,
mock_vim, mock_vnf_instance_save):
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
mock_resource_list.return_value = [fakes.return_vnf_resource()]
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.FORCEFUL)
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.terminate_vnf(self.context, vnf_instance, terminate_vnf_req)
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(1, mock_resource_destroy.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
@mock.patch.object(objects.VnfResource, "destroy")
def test_terminate_vnf_graceful_no_timeout(self, mock_resource_destroy,
mock_resource_list, mock_vim, mock_vnf_instance_save):
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
mock_resource_list.return_value = [fakes.return_vnf_resource()]
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.GRACEFUL)
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.terminate_vnf(self.context, vnf_instance, terminate_vnf_req)
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(1, mock_resource_destroy.call_count)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(vim_client.VimClient, "get_vim")
def test_terminate_vnf_delete_instance_failed(self, mock_vim,
mock_vnf_instance_save):
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.GRACEFUL,
graceful_termination_timeout=10)
self._mock_vnf_manager(fail_method_name='delete')
driver = vnflcm_driver.VnfLcmDriver()
error = self.assertRaises(InfraDriverException, driver.terminate_vnf,
self.context, vnf_instance, terminate_vnf_req)
self.assertEqual("delete failed", str(error))
self.assertEqual(1, mock_vnf_instance_save.call_count)
self.assertEqual(1, self._vnf_manager.invoke.call_count)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(vim_client.VimClient, "get_vim")
def test_terminate_vnf_delete_wait_instance_failed(self, mock_vim,
mock_vnf_instance_save):
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.FORCEFUL)
self._mock_vnf_manager(fail_method_name='delete_wait')
driver = vnflcm_driver.VnfLcmDriver()
error = self.assertRaises(InfraDriverException, driver.terminate_vnf,
self.context, vnf_instance, terminate_vnf_req)
self.assertEqual("delete_wait failed", str(error))
self.assertEqual(2, mock_vnf_instance_save.call_count)
self.assertEqual(2, self._vnf_manager.invoke.call_count)
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_terminate_vnf_delete_vnf_resource_failed(self, mock_resource_list,
mock_vim, mock_vnf_instance_save):
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
terminate_vnf_req = objects.TerminateVnfRequest(
termination_type=fields.VnfInstanceTerminationType.FORCEFUL)
mock_resource_list.return_value = [fakes.return_vnf_resource()]
self._mock_vnf_manager(fail_method_name='delete_vnf_resource')
driver = vnflcm_driver.VnfLcmDriver()
error = self.assertRaises(InfraDriverException, driver.terminate_vnf,
self.context, vnf_instance, terminate_vnf_req)
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)

View File

@ -31,3 +31,14 @@ class VnfInstanceAbstractDriver(object):
:return: None
"""
pass
@abc.abstractmethod
def terminate_vnf(self, context, vnf_instance, terminate_vnf_req):
"""terminate vnf request.
:param context: the request context
:param vnf_instance: object of VnfInstance
:param terminate_vnf_req: object of TerminateVnfRequest
:return: None
"""
pass

View File

@ -14,6 +14,10 @@
# under the License.
import copy
import functools
import inspect
import six
import time
from oslo_config import cfg
from oslo_log import log as logging
@ -24,6 +28,8 @@ from tacker.common import log
from tacker.common import driver_manager
from tacker.common import exceptions
from tacker.common import safe_utils
from tacker.common import utils
from tacker import objects
from tacker.objects import fields
from tacker.vnflcm import abstract_driver
@ -34,6 +40,84 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@utils.expects_func_args('vnf_instance')
def rollback_vnf_instantiated_resources(function):
"""Decorator to rollback resources created during vnf instantiation"""
def _rollback_vnf(vnflcm_driver, context, vnf_instance):
vim_info = vnflcm_utils._get_vim(context,
vnf_instance.vim_connection_info)
vim_connection_info = objects.VimConnectionInfo.obj_from_primitive(
vim_info, context)
LOG.info("Rollback vnf %s", vnf_instance.id)
try:
vnflcm_driver._delete_vnf_instance_resources(context, vnf_instance,
vim_connection_info)
if vnf_instance.instantiated_vnf_info:
vnf_instance.instantiated_vnf_info.reinitialize()
vnflcm_driver._vnf_instance_update(context, vnf_instance,
vim_connection_info=[], task_state=None)
LOG.info("Vnf %s rollback completed successfully", vnf_instance.id)
except Exception as ex:
LOG.error("Unable to rollback vnf instance "
"%s due to error: %s", vnf_instance.id, ex)
@functools.wraps(function)
def decorated_function(self, context, *args, **kwargs):
try:
return function(self, context, *args, **kwargs)
except Exception as exp:
with excutils.save_and_reraise_exception():
wrapped_func = safe_utils.get_wrapped_function(function)
keyed_args = inspect.getcallargs(wrapped_func, self, context,
*args, **kwargs)
vnf_instance = keyed_args['vnf_instance']
LOG.error("Failed to instantiate vnf %(id)s. Error: %(error)s",
{"id": vnf_instance.id,
"error": six.text_type(exp)})
_rollback_vnf(self, context, vnf_instance)
return decorated_function
@utils.expects_func_args('vnf_instance')
def revert_to_error_task_state(function):
"""Decorator to revert task_state to error on failure."""
@functools.wraps(function)
def decorated_function(self, context, *args, **kwargs):
try:
return function(self, context, *args, **kwargs)
except Exception:
with excutils.save_and_reraise_exception():
wrapped_func = safe_utils.get_wrapped_function(function)
keyed_args = inspect.getcallargs(wrapped_func, self, context,
*args, **kwargs)
vnf_instance = keyed_args['vnf_instance']
previous_task_state = vnf_instance.task_state
try:
self._vnf_instance_update(context, vnf_instance,
task_state=fields.VnfInstanceTaskState.ERROR)
LOG.info("Successfully reverted task state from "
"%(state)s to %(error)s on failure for vnf "
"instance %(id)s.",
{"state": previous_task_state,
"id": vnf_instance.id,
"error": fields.VnfInstanceTaskState.ERROR})
except Exception as e:
LOG.warning("Failed to revert task state for vnf "
"instance %(id)s. Error: %(error)s",
{"id": vnf_instance.id, "error": e})
return decorated_function
class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
def __init__(self):
@ -130,6 +214,7 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
vim_connection_info=vim_connection_info)
@log.log
@rollback_vnf_instantiated_resources
def instantiate_vnf(self, context, vnf_instance, instantiate_vnf_req):
vim_connection_info_list = vnflcm_utils.\
@ -151,3 +236,71 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
self._vnf_instance_update(context, vnf_instance,
instantiation_state=fields.VnfInstanceState.INSTANTIATED,
task_state=None)
@log.log
@revert_to_error_task_state
def terminate_vnf(self, context, vnf_instance, terminate_vnf_req):
vim_info = vnflcm_utils._get_vim(context,
vnf_instance.vim_connection_info)
vim_connection_info = objects.VimConnectionInfo.obj_from_primitive(
vim_info, context)
LOG.info("Terminating vnf %s", vnf_instance.id)
try:
self._delete_vnf_instance_resources(context, vnf_instance,
vim_connection_info, terminate_vnf_req=terminate_vnf_req)
vnf_instance.instantiated_vnf_info.reinitialize()
self._vnf_instance_update(context, vnf_instance,
vim_connection_info=[], task_state=None)
LOG.info("Vnf terminated %s successfully", vnf_instance.id)
except Exception as exp:
with excutils.save_and_reraise_exception():
LOG.error("Unable to terminate vnf '%s' instance. "
"Error: %s", vnf_instance.id,
encodeutils.exception_to_unicode(exp))
def _delete_vnf_instance_resources(self, context, vnf_instance,
vim_connection_info,
terminate_vnf_req=None):
if vnf_instance.instantiated_vnf_info and \
vnf_instance.instantiated_vnf_info.instance_id:
instance_id = vnf_instance.instantiated_vnf_info.instance_id
access_info = vim_connection_info.access_info
LOG.info("Deleting stack %(instance)s for vnf %(id)s ",
{"instance": instance_id, "id": vnf_instance.id})
if terminate_vnf_req:
if (terminate_vnf_req.termination_type == 'GRACEFUL' and
terminate_vnf_req.graceful_termination_timeout > 0):
time.sleep(terminate_vnf_req.graceful_termination_timeout)
self._vnf_manager.invoke(vim_connection_info.vim_type,
'delete', plugin=self, context=context,
vnf_id=instance_id, auth_attr=access_info)
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,
vnf_id=instance_id, auth_attr=access_info)
vnf_resources = objects.VnfResourceList.get_by_vnf_instance_id(
context, vnf_instance.id)
for vnf_resource in vnf_resources:
self._vnf_manager.invoke(vim_connection_info.vim_type,
'delete_vnf_instance_resource',
context=context, vnf_instance=vnf_instance,
vim_connection_info=vim_connection_info,
vnf_resource=vnf_resource)
vnf_resource.destroy(context)