Merge "Delete VNF should fail with 409 error"

This commit is contained in:
Zuul 2020-02-05 07:40:05 +00:00 committed by Gerrit Code Review
commit e82d982d7b
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():