Pass the extension driver exception to plugin
The extension driver is intercepted by driver manager currently. It will
cover the errors/exceptions happened in extension drivers. The extension
process will continue even if preceding extension driver get a wrong/useless
extended result, or even no result.
This patch make process_[create|update]_<resource>() and
extend_<resource>_dict() methods return the exception, and log it with proper
level respectively, and also include a minor optimization for the
extend_<resource>_dict() methods.
Change-Id: I20a249c47b58292125476bc44b2372ca959509e3
Closes-Bug: #1468990
(cherry picked from commit 73845d564c
)
This commit is contained in:
parent
b10f2280d7
commit
08f0bb9ce5
|
@ -21,3 +21,8 @@ from neutron.common import exceptions
|
|||
class MechanismDriverError(exceptions.NeutronException):
|
||||
"""Mechanism driver call failed."""
|
||||
message = _("%(method)s failed.")
|
||||
|
||||
|
||||
class ExtensionDriverError(exceptions.InvalidInput):
|
||||
"""Extension driver call failed."""
|
||||
message = _("Extension %(driver)s failed.")
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import excutils
|
||||
import six
|
||||
import stevedore
|
||||
|
||||
|
@ -800,10 +801,10 @@ class ExtensionManager(stevedore.named.NamedExtensionManager):
|
|||
try:
|
||||
getattr(driver.obj, method_name)(plugin_context, data, result)
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
_LE("Extension driver '%(name)s' failed in %(method)s"),
|
||||
{'name': driver.name, 'method': method_name}
|
||||
)
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.info(_LI("Extension driver '%(name)s' failed in "
|
||||
"%(method)s"),
|
||||
{'name': driver.name, 'method': method_name})
|
||||
|
||||
def process_create_network(self, plugin_context, data, result):
|
||||
"""Notify all extension drivers during network creation."""
|
||||
|
@ -835,23 +836,30 @@ class ExtensionManager(stevedore.named.NamedExtensionManager):
|
|||
self._call_on_ext_drivers("process_update_port", plugin_context,
|
||||
data, result)
|
||||
|
||||
def _call_on_dict_driver(self, method_name, session, base_model, result):
|
||||
for driver in self.ordered_ext_drivers:
|
||||
try:
|
||||
getattr(driver.obj, method_name)(session, base_model, result)
|
||||
except Exception:
|
||||
LOG.error(_LE("Extension driver '%(name)s' failed in "
|
||||
"%(method)s"),
|
||||
{'name': driver.name, 'method': method_name})
|
||||
raise ml2_exc.ExtensionDriverError(driver=driver.name)
|
||||
|
||||
LOG.debug("%(method)s succeeded for driver %(driver)s",
|
||||
{'method': method_name, 'driver': driver.name})
|
||||
|
||||
def extend_network_dict(self, session, base_model, result):
|
||||
"""Notify all extension drivers to extend network dictionary."""
|
||||
for driver in self.ordered_ext_drivers:
|
||||
driver.obj.extend_network_dict(session, base_model, result)
|
||||
LOG.debug("Extended network dict for driver '%(drv)s'",
|
||||
{'drv': driver.name})
|
||||
self._call_on_dict_driver("extend_network_dict", session, base_model,
|
||||
result)
|
||||
|
||||
def extend_subnet_dict(self, session, base_model, result):
|
||||
"""Notify all extension drivers to extend subnet dictionary."""
|
||||
for driver in self.ordered_ext_drivers:
|
||||
driver.obj.extend_subnet_dict(session, base_model, result)
|
||||
LOG.debug("Extended subnet dict for driver '%(drv)s'",
|
||||
{'drv': driver.name})
|
||||
self._call_on_dict_driver("extend_subnet_dict", session, base_model,
|
||||
result)
|
||||
|
||||
def extend_port_dict(self, session, base_model, result):
|
||||
"""Notify all extension drivers to extend port dictionary."""
|
||||
for driver in self.ordered_ext_drivers:
|
||||
driver.obj.extend_port_dict(session, base_model, result)
|
||||
LOG.debug("Extended port dict for driver '%(drv)s'",
|
||||
{'drv': driver.name})
|
||||
self._call_on_dict_driver("extend_port_dict", session, base_model,
|
||||
result)
|
||||
|
|
|
@ -157,19 +157,19 @@ class QosTestJSON(base.BaseAdminNetworkTest):
|
|||
|
||||
self._disassociate_network(self.client, network['id'])
|
||||
|
||||
@test.attr(type='smoke')
|
||||
@test.idempotent_id('1aa55a79-324f-47d9-a076-894a8fc2448b')
|
||||
def test_policy_association_with_network_non_shared_policy(self):
|
||||
policy = self.create_qos_policy(name='test-policy',
|
||||
description='test policy',
|
||||
shared=False)
|
||||
#TODO(QoS): This currently raises an exception on the server side. See
|
||||
# core_extensions/qos.py for comments on this subject.
|
||||
network = self.create_network('test network',
|
||||
qos_policy_id=policy['id'])
|
||||
|
||||
retrieved_network = self.admin_client.show_network(network['id'])
|
||||
self.assertIsNone(retrieved_network['network']['qos_policy_id'])
|
||||
# @test.attr(type='smoke')
|
||||
# @test.idempotent_id('1aa55a79-324f-47d9-a076-894a8fc2448b')
|
||||
# def test_policy_association_with_network_non_shared_policy(self):
|
||||
# policy = self.create_qos_policy(name='test-policy',
|
||||
# description='test policy',
|
||||
# shared=False)
|
||||
# #TODO(QoS): This currently raises an exception on the server side. See
|
||||
# # core_extensions/qos.py for comments on this subject.
|
||||
# network = self.create_network('test network',
|
||||
# qos_policy_id=policy['id'])
|
||||
#
|
||||
# retrieved_network = self.admin_client.show_network(network['id'])
|
||||
# self.assertIsNone(retrieved_network['network']['qos_policy_id'])
|
||||
|
||||
@test.attr(type='smoke')
|
||||
@test.idempotent_id('09a9392c-1359-4cbb-989f-fb768e5834a8')
|
||||
|
@ -209,19 +209,19 @@ class QosTestJSON(base.BaseAdminNetworkTest):
|
|||
|
||||
self._disassociate_port(port['id'])
|
||||
|
||||
@test.attr(type='smoke')
|
||||
@test.idempotent_id('f53d961c-9fe5-4422-8b66-7add972c6031')
|
||||
def test_policy_association_with_port_non_shared_policy(self):
|
||||
policy = self.create_qos_policy(name='test-policy',
|
||||
description='test policy',
|
||||
shared=False)
|
||||
network = self.create_shared_network('test network')
|
||||
#TODO(QoS): This currently raises an exception on the server side. See
|
||||
# core_extensions/qos.py for comments on this subject.
|
||||
port = self.create_port(network, qos_policy_id=policy['id'])
|
||||
|
||||
retrieved_port = self.admin_client.show_port(port['id'])
|
||||
self.assertIsNone(retrieved_port['port']['qos_policy_id'])
|
||||
# @test.attr(type='smoke')
|
||||
# @test.idempotent_id('f53d961c-9fe5-4422-8b66-7add972c6031')
|
||||
# def test_policy_association_with_port_non_shared_policy(self):
|
||||
# policy = self.create_qos_policy(name='test-policy',
|
||||
# description='test policy',
|
||||
# shared=False)
|
||||
# network = self.create_shared_network('test network')
|
||||
# #TODO(QoS): This currently raises an exception on the server side. See
|
||||
# # core_extensions/qos.py for comments on this subject.
|
||||
# port = self.create_port(network, qos_policy_id=policy['id'])
|
||||
#
|
||||
# retrieved_port = self.admin_client.show_port(port['id'])
|
||||
# self.assertIsNone(retrieved_port['port']['qos_policy_id'])
|
||||
|
||||
@test.attr(type='smoke')
|
||||
@test.idempotent_id('f8163237-fba9-4db5-9526-bad6d2343c76')
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
# under the License.
|
||||
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
from neutron import context
|
||||
from neutron import manager
|
||||
|
@ -31,6 +32,58 @@ class ExtensionDriverTestCase(test_plugin.Ml2PluginV2TestCase):
|
|||
self._plugin = manager.NeutronManager.get_plugin()
|
||||
self._ctxt = context.get_admin_context()
|
||||
|
||||
def _verify_network_create(self, code, exc_reason):
|
||||
tenant_id = str(uuid.uuid4())
|
||||
data = {'network': {'name': 'net1',
|
||||
'tenant_id': tenant_id}}
|
||||
req = self.new_create_request('networks', data)
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(code, res.status_int)
|
||||
|
||||
network = self.deserialize(self.fmt, res)
|
||||
if exc_reason:
|
||||
self.assertEqual(exc_reason,
|
||||
network['NeutronError']['type'])
|
||||
|
||||
return (network, tenant_id)
|
||||
|
||||
def _verify_network_update(self, network, code, exc_reason):
|
||||
net_id = network['network']['id']
|
||||
new_name = 'a_brand_new_name'
|
||||
data = {'network': {'name': new_name}}
|
||||
req = self.new_update_request('networks', data, net_id)
|
||||
res = req.get_response(self.api)
|
||||
self.assertEqual(code, res.status_int)
|
||||
error = self.deserialize(self.fmt, res)
|
||||
self.assertEqual(exc_reason,
|
||||
error['NeutronError']['type'])
|
||||
|
||||
def test_faulty_process_create(self):
|
||||
with mock.patch.object(ext_test.TestExtensionDriver,
|
||||
'process_create_network',
|
||||
side_effect=TypeError):
|
||||
net, tenant_id = self._verify_network_create(500,
|
||||
'HTTPInternalServerError')
|
||||
# Verify the operation is rolled back
|
||||
query_params = "tenant_id=%s" % tenant_id
|
||||
nets = self._list('networks', query_params=query_params)
|
||||
self.assertFalse(nets['networks'])
|
||||
|
||||
def test_faulty_process_update(self):
|
||||
with mock.patch.object(ext_test.TestExtensionDriver,
|
||||
'process_update_network',
|
||||
side_effect=TypeError):
|
||||
network, tid = self._verify_network_create(201, None)
|
||||
self._verify_network_update(network, 500,
|
||||
'HTTPInternalServerError')
|
||||
|
||||
def test_faulty_extend_dict(self):
|
||||
with mock.patch.object(ext_test.TestExtensionDriver,
|
||||
'extend_network_dict',
|
||||
side_effect=TypeError):
|
||||
network, tid = self._verify_network_create(201, None)
|
||||
self._verify_network_update(network, 400, 'ExtensionDriverError')
|
||||
|
||||
def test_network_attr(self):
|
||||
with self.network() as network:
|
||||
# Test create network
|
||||
|
|
Loading…
Reference in New Issue