Delete VNF should fail with 409 error

If user deletes VNF in "PENDING_CREATE" status, VNF is deleted from
tacker but the resources created by VIM driver are not cleaned up
properly. This patch doesn't allow VNF to be deleted if the status is
in "PENDING_CREATE" status. It will raise 409 error.

APIImpact
Return 409 error instead of 200 when VNF is deleted in
"PENDING_CREATE" status.

Change-Id: I2de9c3dce5a4b3795119e2550aa8acea68940c45
Closes-Bug: #1798726
This commit is contained in:
Shubham Potale 2019-11-21 20:40:56 +05:30 committed by Shubham
parent bdb2d52b3a
commit 938262073f
6 changed files with 86 additions and 28 deletions

View File

@ -731,6 +731,12 @@ vnf_error_reason:
in: body
required: true
type: string
vnf_force_delete_flag:
description: |
VNF attributes object with ``force`` key which can contain true/false value.
in: body
required: false
type: object
vnf_heat_template:
description: |
Heat template which is translated from the VNFD template.

View File

@ -0,0 +1,8 @@
{
"vnf": {
"attributes": {
"force": true
}
}
}

View File

@ -266,6 +266,11 @@ Delete VNF
Deletes a given VNF.
.. note::
Non-admin users are not allowed to delete a VNF in ``PENDING_CREATE`` and
``PENDING_DELETE`` status. In this case, it would return 409 error. Admin
users can delete a VNF in any status with force flag true.
Response Codes
--------------
@ -286,6 +291,15 @@ Request Parameters
.. rest_parameters:: parameters.yaml
- vnf_id: vnf_id_path
- attributes: vnf_force_delete_flag
Request Example
---------------
Admin user can request to delete VNF forcefully.
.. literalinclude:: samples/vnfs/vnfs-delete-request.json
:language: javascript
List VNF resources
==================

View File

@ -492,8 +492,9 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
return vnf_db
def _update_vnf_status_db(self, context, vnf_id, current_statuses,
new_status):
vnf_db = self._get_vnf_db(context, vnf_id, current_statuses)
new_status, vnf_db=None):
if not vnf_db:
vnf_db = self._get_vnf_db(context, vnf_id, current_statuses)
if self.check_vnf_status_legality(vnf_db, vnf_id):
vnf_db.update({'status': new_status})
return vnf_db
@ -508,7 +509,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
def check_vnf_status_legality(vnf_db, vnf_id):
if vnf_db.status == constants.PENDING_DELETE:
error_reason = _("Operation on PENDING_DELETE VNF "
"is not permited. Please contact your "
"is not permitted. Please contact your "
"Administrator.")
raise vnfm.VNFDeleteFailed(reason=error_reason)
if(vnf_db.status in [constants.PENDING_UPDATE,
@ -585,13 +586,25 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
ns_db.NS.vnf_ids.like("%" + vnf_id + "%")).first()
if not force_delete:
if (nss_db is not None and nss_db.status not in
[constants.PENDING_DELETE, constants.ERROR]):
raise vnfm.VNFInUse(vnf_id=vnf_id)
# If vnf is deleted by NFVO, then vnf_id would
# exist in the nss_db otherwise it should be queried from
# vnf db table.
if nss_db is not None:
if nss_db.status not in [constants.PENDING_DELETE,
constants.ERROR]:
raise vnfm.VNFInUse(vnf_id=vnf_id)
else:
vnf_db = self._get_vnf_db(context, vnf_id,
_ACTIVE_UPDATE_ERROR_DEAD)
if (vnf_db is not None and vnf_db.status == constants.
PENDING_CREATE):
raise vnfm.VNFInUse(
message="Operation on PENDING_CREATE VNF is not "
"permitted.")
vnf_db = self._update_vnf_status_db(
context, vnf_id, _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
vnf_db = self._update_vnf_status_db(
context, vnf_id, _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE, vnf_db=vnf_db)
else:
vnf_db = self._update_vnf_status_db_no_check(context,
vnf_id, _ACTIVE_UPDATE_ERROR_DEAD,

View File

@ -15,6 +15,7 @@
from datetime import datetime
import ddt
import mock
from mock import patch
from oslo_utils import uuidutils
@ -131,6 +132,7 @@ class TestVNFMPluginMonitor(db_base.SqlTestCase):
self.assertEqual(1, len(hosting_vnfs))
@ddt.ddt
class TestVNFMPlugin(db_base.SqlTestCase):
def setUp(self):
super(TestVNFMPlugin, self).setUp()
@ -254,7 +256,7 @@ class TestVNFMPlugin(db_base.SqlTestCase):
session.flush()
return vnfd_attr
def _insert_dummy_vnf(self):
def _insert_dummy_vnf(self, status="ACTIVE"):
session = self.context.session
vnf_db = vnfm_db.VNF(
id='6261579e-d6f3-49ad-8bc3-a9cb974778fe',
@ -265,13 +267,13 @@ class TestVNFMPlugin(db_base.SqlTestCase):
vnfd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
placement_attr={'region': 'RegionOne'},
status='ACTIVE',
status=status,
deleted_at=datetime.min)
session.add(vnf_db)
session.flush()
return vnf_db
def _insert_dummy_pending_vnf(self, context):
def _insert_dummy_pending_vnf(self, context, status='PENDING_DELETE'):
session = context.session
vnf_db = vnfm_db.VNF(
id='6261579e-d6f3-49ad-8bc3-a9cb974778fe',
@ -282,7 +284,7 @@ class TestVNFMPlugin(db_base.SqlTestCase):
vnfd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
placement_attr={'region': 'RegionOne'},
status='PENDING_DELETE',
status=status,
deleted_at=datetime.min)
session.add(vnf_db)
session.flush()
@ -510,7 +512,7 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self.vnfm_plugin.create_vnf,
self.context, vnf_obj)
@patch('tacker.vnfm.plugin.VNFMPlugin.delete_vnf')
@patch('tacker.vnfm.plugin.VNFMPlugin._delete_vnf')
def test_create_vnf_fail(self, mock_delete_vnf):
self._insert_dummy_vnf_template()
vnf_obj = utils.get_dummy_vnf_obj()
@ -518,8 +520,9 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self.assertRaises(vnfm.HeatClientException,
self.vnfm_plugin.create_vnf,
self.context, vnf_obj)
vnf_id = self.vnfm_plugin.delete_vnf.call_args[0][1]
mock_delete_vnf.assert_called_once_with(self.context, vnf_id)
vnf_id = self.vnfm_plugin._delete_vnf.call_args[0][1]
mock_delete_vnf.assert_called_once_with(self.context, vnf_id,
force_delete=True)
def test_create_vnf_create_wait_failed_exception(self):
self._insert_dummy_vnf_template()
@ -610,9 +613,10 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self.context,
dummy_vnf_obj['id'])
def test_force_delete_vnf(self):
@ddt.data('PENDING_DELETE', 'PENDING_CREATE')
def test_force_delete_vnf(self, status):
self._insert_dummy_vnf_template()
dummy_vnf_obj = self._insert_dummy_pending_vnf(self.context)
dummy_vnf_obj = self._insert_dummy_pending_vnf(self.context, status)
vnfattr = {'vnf': {'attributes': {'force': True}}}
self.vnfm_plugin.delete_vnf(self.context, dummy_vnf_obj[
'id'], vnf=vnfattr)
@ -624,7 +628,7 @@ class TestVNFMPlugin(db_base.SqlTestCase):
self._cos_db_plugin.create_event.assert_called_with(
self.context, evt_type=constants.RES_EVT_DELETE, res_id=mock.ANY,
res_state=mock.ANY, res_type=constants.RES_TYPE_VNF,
tstamp=mock.ANY, details=mock.ANY)
tstamp=mock.ANY, details="VNF Delete Complete")
def test_force_delete_vnf_non_admin(self):
self._insert_dummy_vnf_template()
@ -667,6 +671,14 @@ class TestVNFMPlugin(db_base.SqlTestCase):
mock.ANY,
mock.ANY)
def test_delete_vnf_failed_with_status_pending_create(self):
self._insert_dummy_vnf_template()
dummy_device_obj_with_pending_create_status = self. \
_insert_dummy_vnf(status="PENDING_CREATE")
self.assertRaises(vnfm.VNFInUse, self.vnfm_plugin.delete_vnf,
self.context,
dummy_device_obj_with_pending_create_status['id'])
def _insert_dummy_ns_template(self):
session = self.context.session
attributes = {

View File

@ -376,7 +376,7 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
'so delete this vnf',
vnf_dict['id'])
with excutils.save_and_reraise_exception():
self.delete_vnf(context, vnf_id)
self._delete_vnf(context, vnf_id, force_delete=True)
vnf_dict['instance_id'] = instance_id
return vnf_dict
@ -621,15 +621,8 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
self.mgmt_delete_post(context, vnf_dict)
self._delete_vnf_post(context, vnf_dict, e)
def delete_vnf(self, context, vnf_id, vnf=None):
def _delete_vnf(self, context, vnf_id, force_delete=False):
# Extract "force_delete" from request's body
force_delete = False
if vnf and vnf['vnf'].get('attributes').get('force'):
force_delete = vnf['vnf'].get('attributes').get('force')
if force_delete and not context.is_admin:
LOG.warning("force delete is admin only operation")
raise exceptions.AdminRequired(reason="Admin only operation")
vnf_dict = self._delete_vnf_pre(context, vnf_id,
force_delete=force_delete)
driver_name, vim_auth = self._get_infra_driver(context, vnf_dict)
@ -673,6 +666,18 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin):
self.spawn_n(self._delete_vnf_wait, context, vnf_dict, vim_auth,
driver_name)
def delete_vnf(self, context, vnf_id, vnf=None):
# Extract "force_delete" from request's body
force_delete = False
if vnf and vnf['vnf'].get('attributes').get('force'):
force_delete = vnf['vnf'].get('attributes').get('force')
if force_delete and not context.is_admin:
LOG.warning("force delete is admin only operation")
raise exceptions.AdminRequired(reason="Admin only operation")
self._delete_vnf(context, vnf_id, force_delete=force_delete)
def _handle_vnf_scaling(self, context, policy):
# validate
def _validate_scaling_policy():