Merge "Adds support force delete for NS"
This commit is contained in:
commit
617c3f884c
@ -133,7 +133,7 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def _get_ns_db(self, context, ns_id, current_statuses, new_status):
|
def _get_ns_db(self, context, ns_id, current_statuses):
|
||||||
try:
|
try:
|
||||||
ns_db = (
|
ns_db = (
|
||||||
self._model_query(context, NS).
|
self._model_query(context, NS).
|
||||||
@ -142,6 +142,9 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
|
|||||||
with_lockmode('update').one())
|
with_lockmode('update').one())
|
||||||
except orm_exc.NoResultFound:
|
except orm_exc.NoResultFound:
|
||||||
raise network_service.NSNotFound(ns_id=ns_id)
|
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})
|
ns_db.update({'status': new_status})
|
||||||
return ns_db
|
return ns_db
|
||||||
|
|
||||||
@ -340,11 +343,17 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
|
|||||||
return ns_dict
|
return ns_dict
|
||||||
|
|
||||||
# reference implementation. needs to be overrided by subclass
|
# 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):
|
with context.session.begin(subtransactions=True):
|
||||||
ns_db = self._get_ns_db(
|
ns_db = self._get_ns_db(
|
||||||
context, ns_id, _ACTIVE_UPDATE_ERROR_DEAD,
|
context, ns_id, _ACTIVE_UPDATE_ERROR_DEAD)
|
||||||
constants.PENDING_DELETE)
|
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)
|
deleted_ns_db = self._make_ns_dict(ns_db)
|
||||||
self._cos_db_plg.create_event(
|
self._cos_db_plg.create_event(
|
||||||
context, res_id=ns_id,
|
context, res_id=ns_id,
|
||||||
@ -355,15 +364,21 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
|
|||||||
return deleted_ns_db
|
return deleted_ns_db
|
||||||
|
|
||||||
def delete_ns_post(self, context, ns_id, mistral_obj,
|
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)
|
ns = self.get_ns(context, ns_id)
|
||||||
nsd_id = ns.get('nsd_id')
|
nsd_id = ns.get('nsd_id')
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
query = (
|
if force_delete:
|
||||||
self._model_query(context, NS).
|
query = (
|
||||||
filter(NS.id == ns_id).
|
self._model_query(context, NS).
|
||||||
filter(NS.status == constants.PENDING_DELETE))
|
filter(NS.id == ns_id))
|
||||||
if mistral_obj and mistral_obj.state == 'ERROR':
|
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})
|
query.update({'status': constants.ERROR})
|
||||||
self._cos_db_plg.create_event(
|
self._cos_db_plg.create_event(
|
||||||
context, res_id=ns_id,
|
context, res_id=ns_id,
|
||||||
@ -385,9 +400,12 @@ class NSPluginDb(network_service.NSPluginBase, db_base.CommonDbMixin):
|
|||||||
details="ns Delete Complete")
|
details="ns Delete Complete")
|
||||||
else:
|
else:
|
||||||
query.delete()
|
query.delete()
|
||||||
template_db = self._get_resource(context, NSD, nsd_id)
|
try:
|
||||||
if template_db.get('template_source') == 'inline':
|
template_db = self._get_resource(context, NSD, nsd_id)
|
||||||
self.delete_nsd(context, 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):
|
def get_ns(self, context, ns_id, fields=None):
|
||||||
ns_db = self._get_resource(context, NS, ns_id)
|
ns_db = self._get_resource(context, NS, ns_id)
|
||||||
|
@ -60,3 +60,7 @@ class NSDNotFound(exceptions.NotFound):
|
|||||||
|
|
||||||
class NSNotFound(exceptions.NotFound):
|
class NSNotFound(exceptions.NotFound):
|
||||||
message = _('NS %(ns_id)s could not be found')
|
message = _('NS %(ns_id)s could not be found')
|
||||||
|
|
||||||
|
|
||||||
|
class NSInUse(exceptions.InUse):
|
||||||
|
message = _('NS %(ns_id)s in use')
|
||||||
|
@ -96,6 +96,8 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
|
|||||||
|
|
||||||
def _add_delete_vnf_tasks(self, ns, vnffg_ids=None):
|
def _add_delete_vnf_tasks(self, ns, vnffg_ids=None):
|
||||||
vnfds = ns['vnfd_details']
|
vnfds = ns['vnfd_details']
|
||||||
|
vnf_attr = {'vnf': {'attributes': {
|
||||||
|
'force': ns.get('force_delete', False)}}}
|
||||||
task_dict = dict()
|
task_dict = dict()
|
||||||
for vnfd_name, vnfd_info in (vnfds).items():
|
for vnfd_name, vnfd_info in (vnfds).items():
|
||||||
nodes = vnfd_info['instances']
|
nodes = vnfd_info['instances']
|
||||||
@ -104,6 +106,7 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
|
|||||||
task_dict[task] = {
|
task_dict[task] = {
|
||||||
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_{0}'
|
'action': 'tacker.delete_vnf vnf=<% $.vnf_id_{0}'
|
||||||
'%>'.format(node),
|
'%>'.format(node),
|
||||||
|
'input': {'body': vnf_attr},
|
||||||
}
|
}
|
||||||
if vnffg_ids and len(vnffg_ids):
|
if vnffg_ids and len(vnffg_ids):
|
||||||
task_dict[task].update({'join': 'all'})
|
task_dict[task].update({'join': 'all'})
|
||||||
@ -271,5 +274,6 @@ class WorkflowGenerator(workflow_generator.WorkflowGeneratorBase):
|
|||||||
ns_dict['vnffg_details'] = vnffg_ids
|
ns_dict['vnffg_details'] = vnffg_ids
|
||||||
self.definition[self.wf_identifier]['tasks'].update(
|
self.definition[self.wf_identifier]['tasks'].update(
|
||||||
self._add_delete_vnffg_task(ns_dict))
|
self._add_delete_vnffg_task(ns_dict))
|
||||||
|
ns_dict['force_delete'] = ns.get('force_delete', False)
|
||||||
self.definition[self.wf_identifier]['tasks'].update(
|
self.definition[self.wf_identifier]['tasks'].update(
|
||||||
self._add_delete_vnf_tasks(ns_dict, vnffg_ids))
|
self._add_delete_vnf_tasks(ns_dict, vnffg_ids))
|
||||||
|
@ -31,6 +31,7 @@ from toscaparser.tosca_template import ToscaTemplate
|
|||||||
|
|
||||||
from tacker._i18n import _
|
from tacker._i18n import _
|
||||||
from tacker.common import driver_manager
|
from tacker.common import driver_manager
|
||||||
|
from tacker.common import exceptions
|
||||||
from tacker.common import log
|
from tacker.common import log
|
||||||
from tacker.common import utils
|
from tacker.common import utils
|
||||||
from tacker import context as t_context
|
from tacker import context as t_context
|
||||||
@ -896,15 +897,23 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
|
|||||||
raise cs.ParamYAMLInputMissing()
|
raise cs.ParamYAMLInputMissing()
|
||||||
|
|
||||||
@log.log
|
@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)
|
ns = super(NfvoPlugin, self).get_ns(context, ns_id)
|
||||||
LOG.debug("Deleting ns: %s", ns)
|
LOG.debug("Deleting ns: %s", ns)
|
||||||
vim_res = self.vim_client.get_vim(context, ns['vim_id'])
|
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']
|
driver_type = vim_res['vim_type']
|
||||||
workflow = None
|
workflow = None
|
||||||
try:
|
try:
|
||||||
if ns['vnf_ids']:
|
if ns['vnf_ids']:
|
||||||
|
ns['force_delete'] = force_delete
|
||||||
workflow = self._vim_drivers.invoke(
|
workflow = self._vim_drivers.invoke(
|
||||||
driver_type,
|
driver_type,
|
||||||
'prepare_and_create_workflow',
|
'prepare_and_create_workflow',
|
||||||
@ -969,11 +978,12 @@ class NfvoPlugin(nfvo_db_plugin.NfvoPluginDb, vnffg_db.VnffgPluginDbMixin,
|
|||||||
workflow_id=workflow['id'],
|
workflow_id=workflow['id'],
|
||||||
auth_dict=self.get_auth_dict(context))
|
auth_dict=self.get_auth_dict(context))
|
||||||
super(NfvoPlugin, self).delete_ns_post(context, ns_id, exec_obj,
|
super(NfvoPlugin, self).delete_ns_post(context, ns_id, exec_obj,
|
||||||
error_reason)
|
error_reason,
|
||||||
|
force_delete=force_delete)
|
||||||
|
|
||||||
if workflow:
|
if workflow:
|
||||||
self.spawn_n(_delete_ns_wait, ns['id'], mistral_execution.id)
|
self.spawn_n(_delete_ns_wait, ns['id'], mistral_execution.id)
|
||||||
else:
|
else:
|
||||||
super(NfvoPlugin, self).delete_ns_post(
|
super(NfvoPlugin, self).delete_ns_post(
|
||||||
context, ns_id, None, None)
|
context, ns_id, None, None, force_delete=force_delete)
|
||||||
return ns['id']
|
return ns['id']
|
||||||
|
@ -174,9 +174,13 @@ def get_dummy_create_workflow():
|
|||||||
'on-success': [{'delete_vnf_VNF1': '<% $.status_VNF1='
|
'on-success': [{'delete_vnf_VNF1': '<% $.status_VNF1='
|
||||||
'"ERROR" %>'}]},
|
'"ERROR" %>'}]},
|
||||||
'delete_vnf_VNF1': {
|
'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': {
|
'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': {
|
'type': 'direct', 'output': {
|
||||||
'status_VNF1': '<% $.status_VNF1 %>',
|
'status_VNF1': '<% $.status_VNF1 %>',
|
||||||
'status_VNF2': '<% $.status_VNF2 %>',
|
'status_VNF2': '<% $.status_VNF2 %>',
|
||||||
@ -268,9 +272,13 @@ def get_dummy_create_vnffg_ns_workflow():
|
|||||||
'<% task(create_ns_VNF2).result.vnf.id %>'},
|
'<% task(create_ns_VNF2).result.vnf.id %>'},
|
||||||
'on-success': ['wait_vnf_active_VNF2']},
|
'on-success': ['wait_vnf_active_VNF2']},
|
||||||
'delete_vnf_VNF1': {
|
'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': {
|
'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',
|
'type': 'direct',
|
||||||
'output': {
|
'output': {
|
||||||
'status_VNF1': '<% $.status_VNF1 %>',
|
'status_VNF1': '<% $.status_VNF1 %>',
|
||||||
@ -301,7 +309,9 @@ def get_dummy_delete_workflow():
|
|||||||
'input': ['vnf_id_VNF1'],
|
'input': ['vnf_id_VNF1'],
|
||||||
'tasks': {
|
'tasks': {
|
||||||
'delete_vnf_VNF1': {
|
'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'}}
|
'type': 'direct'}}
|
||||||
|
|
||||||
|
|
||||||
@ -312,7 +322,9 @@ def get_dummy_delete_vnffg_ns_workflow():
|
|||||||
'tasks': {
|
'tasks': {
|
||||||
'delete_vnf_VNF1': {
|
'delete_vnf_VNF1': {
|
||||||
'join': 'all',
|
'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': {
|
'delete_vnffg_VNFFG1': {
|
||||||
'action': 'tacker.delete_vnffg vnffg='
|
'action': 'tacker.delete_vnffg vnffg='
|
||||||
'<% $.VNFFG1 %>',
|
'<% $.VNFFG1 %>',
|
||||||
|
@ -21,6 +21,7 @@ from oslo_utils import uuidutils
|
|||||||
|
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
|
from tacker.common import exceptions
|
||||||
from tacker import context
|
from tacker import context
|
||||||
from tacker.db.common_services import common_services_db_plugin
|
from tacker.db.common_services import common_services_db_plugin
|
||||||
from tacker.db.nfvo import nfvo_db
|
from tacker.db.nfvo import nfvo_db
|
||||||
@ -1043,13 +1044,13 @@ class TestNfvoPlugin(db_base.SqlTestCase):
|
|||||||
session.flush()
|
session.flush()
|
||||||
return nsd_template
|
return nsd_template
|
||||||
|
|
||||||
def _insert_dummy_ns(self):
|
def _insert_dummy_ns(self, status='ACTIVE'):
|
||||||
session = self.context.session
|
session = self.context.session
|
||||||
ns = ns_db.NS(
|
ns = ns_db.NS(
|
||||||
id='ba6bf017-f6f7-45f1-a280-57b073bf78ea',
|
id='ba6bf017-f6f7-45f1-a280-57b073bf78ea',
|
||||||
name='dummy_ns',
|
name='dummy_ns',
|
||||||
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
|
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
|
||||||
status='ACTIVE',
|
status=status,
|
||||||
nsd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
|
nsd_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
|
||||||
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||||
description='dummy_ns_description',
|
description='dummy_ns_description',
|
||||||
@ -1269,4 +1270,57 @@ class TestNfvoPlugin(db_base.SqlTestCase):
|
|||||||
self.nfvo_plugin.delete_ns(self.context,
|
self.nfvo_plugin.delete_ns(self.context,
|
||||||
DUMMY_NS_2)
|
DUMMY_NS_2)
|
||||||
mock_delete_ns_post.assert_called_with(
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user