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'], 'required': ['flavourId'],
'additionalProperties': False, '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) 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): 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): def heal(self, request, id, body):
raise webob.exc.HTTPNotImplemented() raise webob.exc.HTTPNotImplemented()

View File

@ -424,6 +424,20 @@ class Conductor(manager.Manager):
vnf_package.save() 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): def init(args, **kwargs):
CONF(args=args, project='tacker', CONF(args=args, project='tacker',

View File

@ -38,3 +38,14 @@ class VNFLcmRPCAPI(object):
return rpc_method(context, 'instantiate', return rpc_method(context, 'instantiate',
vnf_instance=vnf_instance, vnf_instance=vnf_instance,
instantiate_vnf=instantiate_vnf) 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.vim_connection')
__import__('tacker.objects.instantiate_vnf_req') __import__('tacker.objects.instantiate_vnf_req')
__import__('tacker.objects.vnf_resources') __import__('tacker.objects.vnf_resources')
__import__('tacker.objects.terminate_vnf_req')

View File

@ -166,3 +166,14 @@ class IpAddressType(BaseTackerEnum):
class IpAddressTypeField(BaseEnumField): class IpAddressTypeField(BaseEnumField):
AUTO_TYPE = IpAddressType() 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 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 @base.TackerObjectRegistry.register
class VnfExtCpInfo(base.TackerObject, base.TackerObjectDictCompat, 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 import context
from tacker.glance_store import store as glance_store from tacker.glance_store import store as glance_store
from tacker import objects 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.conductor import fakes
from tacker.tests.unit.db.base import SqlTestCase from tacker.tests.unit.db.base import SqlTestCase
from tacker.tests.unit.objects import fakes as fake_obj from tacker.tests.unit.objects import fakes as fake_obj
@ -73,8 +73,8 @@ class TestConductor(SqlTestCase):
'tacker.vnflcm.vnflcm_driver.VnfLcmDriver', fake_vnflcm_driver) 'tacker.vnflcm.vnflcm_driver.VnfLcmDriver', fake_vnflcm_driver)
def _create_vnf_package(self): def _create_vnf_package(self):
vnfpkgm = vnf_package.VnfPackage(context=self.context, vnfpkgm = objects.VnfPackage(context=self.context,
**fakes.VNF_PACKAGE_DATA) **fakes.VNF_PACKAGE_DATA)
vnfpkgm.create() vnfpkgm.create()
return vnfpkgm return vnfpkgm
@ -251,10 +251,101 @@ class TestConductor(SqlTestCase):
mock_log.error.assert_called_once_with(expected_log, mock_log.error.assert_called_once_with(expected_log,
vnf_package_vnfd.package_uuid) 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(os, 'remove')
@mock.patch.object(shutil, 'rmtree') @mock.patch.object(shutil, 'rmtree')
@mock.patch.object(os.path, 'exists') @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, def test_run_cleanup_vnf_packages(self, mock_get_by_filter,
mock_exists, mock_rmtree, mock_exists, mock_rmtree,
mock_remove): 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) resp = req.get_response(self.app)
self.assertEqual(http_client.METHOD_NOT_ALLOWED, resp.status_code) 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.common import utils
from tacker import context from tacker import context
from tacker import objects from tacker import objects
from tacker.objects import fields
from tacker.tests.unit.db import base as db_base from tacker.tests.unit.db import base as db_base
from tacker.tests.unit.vnflcm import fakes from tacker.tests.unit.vnflcm import fakes
from tacker.tests import uuidsentinel from tacker.tests import uuidsentinel
from tacker.vnflcm import vnflcm_driver from tacker.vnflcm import vnflcm_driver
from tacker.vnfm import vim_client
class InfraDriverException(Exception): class InfraDriverException(Exception):
@ -256,7 +258,7 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(expected_error % vnf_instance_obj.id, str(error)) self.assertEqual(expected_error % vnf_instance_obj.id, str(error))
self.assertEqual("NOT_INSTANTIATED", self.assertEqual("NOT_INSTANTIATED",
vnf_instance_obj.instantiation_state) 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) self.assertEqual(2, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar) 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(expected_error % vnf_instance_obj.id, str(error))
self.assertEqual("NOT_INSTANTIATED", self.assertEqual("NOT_INSTANTIATED",
vnf_instance_obj.instantiation_state) vnf_instance_obj.instantiation_state)
self.assertEqual(1, mock_vnf_instance_save.call_count) self.assertEqual(3, mock_vnf_instance_save.call_count)
self.assertEqual(3, self._vnf_manager.invoke.call_count) self.assertEqual(5, self._vnf_manager.invoke.call_count)
shutil.rmtree(fake_csar) shutil.rmtree(fake_csar)
@ -334,3 +336,106 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(2, mock_create.call_count) self.assertEqual(2, mock_create.call_count)
self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state) self.assertEqual("INSTANTIATED", vnf_instance_obj.instantiation_state)
shutil.rmtree(fake_csar) 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 :return: None
""" """
pass 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. # under the License.
import copy import copy
import functools
import inspect
import six
import time
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging 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 driver_manager
from tacker.common import exceptions from tacker.common import exceptions
from tacker.common import safe_utils
from tacker.common import utils
from tacker import objects from tacker import objects
from tacker.objects import fields from tacker.objects import fields
from tacker.vnflcm import abstract_driver from tacker.vnflcm import abstract_driver
@ -34,6 +40,84 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF 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): class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
def __init__(self): def __init__(self):
@ -130,6 +214,7 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
vim_connection_info=vim_connection_info) vim_connection_info=vim_connection_info)
@log.log @log.log
@rollback_vnf_instantiated_resources
def instantiate_vnf(self, context, vnf_instance, instantiate_vnf_req): def instantiate_vnf(self, context, vnf_instance, instantiate_vnf_req):
vim_connection_info_list = vnflcm_utils.\ vim_connection_info_list = vnflcm_utils.\
@ -151,3 +236,71 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
self._vnf_instance_update(context, vnf_instance, self._vnf_instance_update(context, vnf_instance,
instantiation_state=fields.VnfInstanceState.INSTANTIATED, instantiation_state=fields.VnfInstanceState.INSTANTIATED,
task_state=None) 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)