Adds support force delete for NS

This patch adds '--force' parameter to NS delete command,
to force delete NS instances.
e.g: openstack ns delete test_ns --force

blueprint force-delete-resources

Change-Id: I739ad1e1e764f38edffa3d7fa423da370b53242b
This commit is contained in:
dharmendra 2019-06-20 06:39:39 +00:00
parent 3a198b0ba6
commit 216b3859ab
6 changed files with 128 additions and 26 deletions

View File

@ -133,7 +133,7 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
else:
raise
def _get_ns_db(self, context, ns_id, current_statuses, new_status):
def _get_ns_db(self, context, ns_id, current_statuses):
try:
ns_db = (
self._model_query(context, NS).
@ -142,6 +142,9 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
with_lockmode('update').one())
except orm_exc.NoResultFound:
raise network_service.NSNotFound(ns_id=ns_id)
return ns_db
def _update_ns_db(self, ns_db, new_status):
ns_db.update({'status': new_status})
return ns_db
@ -340,11 +343,17 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
return ns_dict
# reference implementation. needs to be overrided by subclass
def delete_ns_pre(self, context, ns_id):
def delete_ns_pre(self, context, ns_id, force_delete=False):
with context.session.begin(subtransactions=True):
ns_db = self._get_ns_db(
context, ns_id, _ACTIVE_UPDATE_ERROR_DEAD,
constants.PENDING_DELETE)
context, ns_id, _ACTIVE_UPDATE_ERROR_DEAD)
if not force_delete:
if (ns_db is not None and ns_db.status in
[constants.PENDING_DELETE,
constants.PENDING_CREATE,
constants.PENDING_UPDATE]):
raise network_service.NSInUse(ns_id=ns_id)
ns_db = self._update_ns_db(ns_db, constants.PENDING_DELETE)
deleted_ns_db = self._make_ns_dict(ns_db)
self._cos_db_plg.create_event(
context, res_id=ns_id,
@ -355,15 +364,21 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
return deleted_ns_db
def delete_ns_post(self, context, ns_id, mistral_obj,
error_reason, soft_delete=True):
error_reason, soft_delete=True, force_delete=False):
ns = self.get_ns(context, ns_id)
nsd_id = ns.get('nsd_id')
with context.session.begin(subtransactions=True):
query = (
self._model_query(context, NS).
filter(NS.id == ns_id).
filter(NS.status == constants.PENDING_DELETE))
if mistral_obj and mistral_obj.state == 'ERROR':
if force_delete:
query = (
self._model_query(context, NS).
filter(NS.id == ns_id))
else:
query = (
self._model_query(context, NS).
filter(NS.id == ns_id).
filter(NS.status == constants.PENDING_DELETE))
if not force_delete and (mistral_obj
and mistral_obj.state == 'ERROR'):
query.update({'status': constants.ERROR})
self._cos_db_plg.create_event(
context, res_id=ns_id,
@ -385,9 +400,12 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
details="ns Delete Complete")
else:
query.delete()
template_db = self._get_resource(context, NSD, nsd_id)
if template_db.get('template_source') == 'inline':
self.delete_nsd(context, nsd_id)
try:
template_db = self._get_resource(context, NSD, nsd_id)
if template_db.get('template_source') == 'inline':
self.delete_nsd(context, nsd_id)
except orm_exc.NoResultFound:
pass
def get_ns(self, context, ns_id, fields=None):
ns_db = self._get_resource(context, NS, ns_id)

View File

@ -60,3 +60,7 @@ class NSDNotFound(exceptions.NotFound):
class NSNotFound(exceptions.NotFound):
message = _('NS %(ns_id)s could not be found')
class NSInUse(exceptions.InUse):
message = _('NS %(ns_id)s in use')

View File

@ -96,6 +96,8 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
def _add_delete_vnf_tasks(self, ns, vnffg_ids=None):
vnfds = ns['vnfd_details']
vnf_attr = {'vnf': {'attributes': {
'force': ns.get('force_delete', False)}}}
task_dict = dict()
for vnfd_name, vnfd_info in (vnfds).items():
nodes = vnfd_info['instances']
@ -104,6 +106,7 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
task_dict[task] = {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_{0}'
'%>'.format(node),
'input': {'body': vnf_attr},
}
if vnffg_ids and len(vnffg_ids):
task_dict[task].update({'join': 'all'})
@ -271,5 +274,6 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
ns_dict['vnffg_details'] = vnffg_ids
self.definition[self.wf_identifier]['tasks'].update(
self._add_delete_vnffg_task(ns_dict))
ns_dict['force_delete'] = ns.get('force_delete', False)
self.definition[self.wf_identifier]['tasks'].update(
self._add_delete_vnf_tasks(ns_dict, vnffg_ids))

View File

@ -31,6 +31,7 @@ from toscaparser.tosca_template import ToscaTemplate
from tacker._i18n import _
from tacker.common import driver_manager
from tacker.common import exceptions
from tacker.common import log
from tacker.common import utils
from tacker import context as t_context
@ -896,15 +897,23 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
raise cs.ParamYAMLInputMissing()
@log.log
def delete_ns(self, context, ns_id):
def delete_ns(self, context, ns_id, ns=None):
# Extract "force_delete" from request's body
force_delete = False
if ns and ns.get('ns', {}).get('attributes', {}).get('force'):
force_delete = ns['ns'].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")
ns = super(NfvoPlugin, self).get_ns(context, ns_id)
LOG.debug("Deleting ns: %s", ns)
vim_res = self.vim_client.get_vim(context, ns['vim_id'])
super(NfvoPlugin, self).delete_ns_pre(context, ns_id)
super(NfvoPlugin, self).delete_ns_pre(context, ns_id, force_delete)
driver_type = vim_res['vim_type']
workflow = None
try:
if ns['vnf_ids']:
ns['force_delete'] = force_delete
workflow = self._vim_drivers.invoke(
driver_type,
'prepare_and_create_workflow',
@ -969,11 +978,12 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
workflow_id=workflow['id'],
auth_dict=self.get_auth_dict(context))
super(NfvoPlugin, self).delete_ns_post(context, ns_id, exec_obj,
error_reason)
error_reason,
force_delete=force_delete)
if workflow:
self.spawn_n(_delete_ns_wait, ns['id'], mistral_execution.id)
else:
super(NfvoPlugin, self).delete_ns_post(
context, ns_id, None, None)
context, ns_id, None, None, force_delete=force_delete)
return ns['id']

View File

@ -174,9 +174,13 @@ def get_dummy_create_workflow():
'on-success': [{'delete_vnf_VNF1': '<% $.status_VNF1='
'"ERROR" %>'}]},
'delete_vnf_VNF1': {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>'},
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>',
'input': {'body': {'vnf': {'attributes': {
'force': False}}}}},
'delete_vnf_VNF2': {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF2%>'}},
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF2%>',
'input': {'body': {'vnf': {'attributes': {
'force': False}}}}}},
'type': 'direct', 'output': {
'status_VNF1': '<% $.status_VNF1 %>',
'status_VNF2': '<% $.status_VNF2 %>',
@ -268,9 +272,13 @@ def get_dummy_create_vnffg_ns_workflow():
'<% task(create_ns_VNF2).result.vnf.id %>'},
'on-success': ['wait_vnf_active_VNF2']},
'delete_vnf_VNF1': {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>'},
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>',
'input': {'body': {'vnf': {'attributes': {
'force': False}}}}},
'delete_vnf_VNF2': {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF2%>'}},
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF2%>',
'input': {'body': {'vnf': {'attributes': {
'force': False}}}}}},
'type': 'direct',
'output': {
'status_VNF1': '<% $.status_VNF1 %>',
@ -301,7 +309,9 @@ def get_dummy_delete_workflow():
'input': ['vnf_id_VNF1'],
'tasks': {
'delete_vnf_VNF1': {
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>'}},
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>',
'input': {'body': {'vnf': {'attributes': {
'force': False}}}}}},
'type': 'direct'}}
@ -312,7 +322,9 @@ def get_dummy_delete_vnffg_ns_workflow():
'tasks': {
'delete_vnf_VNF1': {
'join': 'all',
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>'},
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_VNF1%>',
'input': {'body': {'vnf': {'attributes': {
'force': False}}}}},
'delete_vnffg_VNFFG1': {
'action': 'tacker.delete_vnffg vnffg='
'<% $.VNFFG1 %>',

View File

@ -21,6 +21,7 @@ from oslo_utils import uuidutils
from mock import patch
from tacker.common import exceptions
from tacker import context
from tacker.db.common_services import common_services_db_plugin
from tacker.db.nfvo import nfvo_db
@ -1043,13 +1044,13 @@ class TestNfvoPlugin(db_base.SqlTestCase):
session.flush()
return nsd_template
def _insert_dummy_ns(self):
def _insert_dummy_ns(self, status='ACTIVE'):
session = self.context.session
ns = ns_db.NS(
id='ba6bf017-f6f7-45f1-a280-57b073bf78ea',
name='dummy_ns',
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
status='ACTIVE',
status=status,
nsd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
description='dummy_ns_description',
@ -1269,4 +1270,57 @@ class TestNfvoPlugin(db_base.SqlTestCase):
self.nfvo_plugin.delete_ns(self.context,
DUMMY_NS_2)
mock_delete_ns_post.assert_called_with(
self.context, DUMMY_NS_2, None, None)
self.context, DUMMY_NS_2, None, None, force_delete=False)
@mock.patch.object(nfvo_plugin.NfvoPlugin, 'get_auth_dict')
@mock.patch.object(vim_client.VimClient, 'get_vim')
@mock.patch.object(nfvo_plugin.NfvoPlugin, '_get_by_name')
def test_delete_ns_force(self, mock_get_by_name,
mock_get_vim, mock_auth_dict):
self._insert_dummy_vim()
self._insert_dummy_ns_template()
self._insert_dummy_ns(status='PENDING_DELETE')
mock_auth_dict.return_value = {
'auth_url': 'http://127.0.0.1',
'token': 'DummyToken',
'project_domain_name': 'dummy_domain',
'project_name': 'dummy_project'
}
nsattr = {'ns': {'attributes': {'force': True}}}
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock_get_by_name.return_value = get_by_name()
result = self.nfvo_plugin.delete_ns(self.context,
'ba6bf017-f6f7-45f1-a280-57b073bf78ea', ns=nsattr)
self.assertIsNotNone(result)
@mock.patch.object(nfvo_plugin.NfvoPlugin, 'get_auth_dict')
@mock.patch.object(vim_client.VimClient, 'get_vim')
@mock.patch.object(nfvo_plugin.NfvoPlugin, '_get_by_name')
def test_delete_ns_force_non_admin(self, mock_get_by_name,
mock_get_vim, mock_auth_dict):
self._insert_dummy_vim()
self._insert_dummy_ns_template()
self._insert_dummy_ns(status='PENDING_DELETE')
mock_auth_dict.return_value = {
'auth_url': 'http://127.0.0.1',
'token': 'DummyToken',
'project_domain_name': 'dummy_domain',
'project_name': 'dummy_project'
}
nsattr = {'ns': {'attributes': {'force': True}}}
with patch.object(TackerManager, 'get_service_plugins') as \
mock_plugins:
mock_plugins.return_value = {'VNFM': FakeVNFMPlugin()}
mock_get_by_name.return_value = get_by_name()
non_admin_context = context.Context(user_id=None,
tenant_id=None,
is_admin=False)
self.assertRaises(exceptions.AdminRequired,
self.nfvo_plugin.delete_ns,
non_admin_context,
'ba6bf017-f6f7-45f1-a280-57b073bf78ea',
ns=nsattr)