Merge "Deal with unknown exceptions during auto allocation"

This commit is contained in:
Jenkins 2016-09-03 00:04:31 +00:00 committed by Gerrit Code Review
commit 3bf4ab7ffd
3 changed files with 122 additions and 28 deletions

View File

@ -17,7 +17,6 @@
from neutron_lib import exceptions as n_exc
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import excutils
from sqlalchemy import sql
from neutron._i18n import _, _LE
@ -153,9 +152,6 @@ class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
def _build_topology(self, context, tenant_id, default_external_network):
"""Build the network topology and returns its network UUID."""
network_id = None
router_id = None
subnets = None
try:
subnets = self._provision_tenant_private_network(
context, tenant_id)
@ -165,17 +161,16 @@ class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
network_id = self._save(
context, tenant_id, network_id, router['id'], subnets)
return network_id
except Exception as e:
with excutils.save_and_reraise_exception():
# FIXME(armax): defensively catch all errors and let
# the caller retry the operation, if it can be retried.
# This catch-all should no longer be necessary once
# bug #1612798 is solved; any other error should just
# surface up to the user and be dealt with as a bug.
if db_api.is_retriable(e):
self._cleanup(
context, network_id=network_id,
router_id=router_id, subnets=subnets)
except exceptions.UnknownProvisioningError as e:
# Clean partially provisioned topologies, and reraise the
# error. If it can be retried, so be it.
LOG.error(_LE("Unknown error while provisioning topology for "
"tenant %(tenant_id)s. Reason: %(reason)s"),
{'tenant_id': tenant_id, 'reason': e})
self._cleanup(
context, network_id=e.network_id,
router_id=e.router_id, subnets=e.subnets)
raise e.error
def _check_requirements(self, context, tenant_id):
"""Raise if requirements are not met."""
@ -291,6 +286,9 @@ class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
self._cleanup(context, network['id'])
raise exceptions.AutoAllocationFailure(
reason=_("Unable to provide tenant private network"))
except Exception as e:
network_id = network['id'] if network else None
raise exceptions.UnknownProvisioningError(e, network_id=network_id)
def _provision_external_connectivity(
self, context, default_external_network, subnets, tenant_id):
@ -316,15 +314,17 @@ class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
"%(tenant_id)s because of router errors. "
"Reason: %(reason)s"),
{'tenant_id': tenant_id, 'reason': e})
if router:
router_id = router['id']
else:
router_id = None
router_id = router['id'] if router else None
self._cleanup(context,
network_id=subnets[0]['network_id'],
router_id=router_id, subnets=attached_subnets)
raise exceptions.AutoAllocationFailure(
reason=_("Unable to provide external connectivity"))
except Exception as e:
router_id = router['id'] if router else None
raise exceptions.UnknownProvisioningError(
e, network_id=subnets[0]['network_id'],
router_id=router_id, subnets=subnets)
def _save(self, context, tenant_id, network_id, router_id, subnets):
"""Save auto-allocated topology, or revert in case of DB errors."""
@ -354,6 +354,10 @@ class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
context, network_id=network_id,
router_id=router_id, subnets=subnets)
network_id = self._get_auto_allocated_network(context, tenant_id)
except Exception as e:
raise exceptions.UnknownProvisioningError(
e, network_id=network_id,
router_id=router_id, subnets=subnets)
return network_id
# FIXME(kevinbenton): get rid of the retry once bug/1612798 is resolved

View File

@ -25,3 +25,13 @@ class AutoAllocationFailure(n_exc.Conflict):
class DefaultExternalNetworkExists(n_exc.Conflict):
message = _("A default external network already exists: %(net_id)s.")
class UnknownProvisioningError(Exception):
"""To track unknown errors and partial provisioning steps."""
def __init__(self, error, network_id=None, router_id=None, subnets=None):
self.error = error
self.network_id = network_id
self.router_id = router_id
self.subnets = subnets

View File

@ -12,12 +12,15 @@
# limitations under the License.
import mock
import testtools
from neutron_lib import exceptions as n_exc
from oslo_db import exception as db_exc
from oslo_utils import uuidutils
from neutron.common import exceptions as c_exc
from neutron import context
from neutron.plugins.common import utils
from neutron.services.auto_allocate import db
from neutron.services.auto_allocate import exceptions
from neutron.tests.unit import testlib_api
@ -93,21 +96,98 @@ class AutoAllocateTestCase(testlib_api.SqlTestCaseLight):
self.ctx, 'foo_tenant')
g.assert_called_once_with(self.ctx, network_id)
def _test__build_topology(self, exception):
def _test__build_topology(self, method, provisioning_exception):
with mock.patch.object(self.mixin,
'_provision_tenant_private_network',
side_effect=exception), \
method,
side_effect=provisioning_exception), \
mock.patch.object(self.mixin, '_cleanup') as f:
self.assertRaises(exception,
self.assertRaises(provisioning_exception.error,
self.mixin._build_topology,
self.ctx, mock.ANY, 'foo_net')
return f.call_count
f.assert_called_once_with(
self.ctx,
network_id=provisioning_exception.network_id,
router_id=provisioning_exception.router_id,
subnets=provisioning_exception.subnets
)
def test__build_topology_retriable_exception(self):
self.assertTrue(self._test__build_topology(db_exc.DBConnectionError))
def test__build_topology_provisioning_error_no_toplogy(self):
provisioning_exception = exceptions.UnknownProvisioningError(
db_exc.DBError)
self._test__build_topology(
'_provision_tenant_private_network',
provisioning_exception)
def test__build_topology_non_retriable_exception(self):
self.assertFalse(self._test__build_topology(Exception))
def test__build_topology_provisioning_error_network_only(self):
provisioning_exception = exceptions.UnknownProvisioningError(
Exception, network_id='foo')
self._test__build_topology(
'_provision_tenant_private_network',
provisioning_exception)
def test__build_topology_error_only_network_again(self):
provisioning_exception = exceptions.UnknownProvisioningError(
AttributeError, network_id='foo')
with mock.patch.object(self.mixin,
'_provision_tenant_private_network') as f:
f.return_value = [{'network_id': 'foo'}]
self._test__build_topology(
'_provision_external_connectivity',
provisioning_exception)
def test__build_topology_error_network_with_router(self):
provisioning_exception = exceptions.UnknownProvisioningError(
KeyError, network_id='foo_n', router_id='foo_r')
with mock.patch.object(self.mixin,
'_provision_tenant_private_network') as f:
f.return_value = [{'network_id': 'foo_n'}]
self._test__build_topology(
'_provision_external_connectivity',
provisioning_exception)
def test__build_topology_error_network_with_router_and_interfaces(self):
provisioning_exception = exceptions.UnknownProvisioningError(
db_exc.DBConnectionError,
network_id='foo_n', router_id='foo_r', subnets=[{'id': 'foo_s'}])
with mock.patch.object(self.mixin,
'_provision_tenant_private_network') as f,\
mock.patch.object(self.mixin,
'_provision_external_connectivity') as g:
f.return_value = [{'network_id': 'foo_n'}]
g.return_value = {'id': 'foo_r'}
self._test__build_topology(
'_save',
provisioning_exception)
def test__save_with_provisioning_error(self):
with mock.patch.object(utils, "update_network", side_effect=Exception):
with testtools.ExpectedException(
exceptions.UnknownProvisioningError) as e:
self.mixin._save(self.ctx, 'foo_t', 'foo_n', 'foo_r',
[{'id': 'foo_s'}])
self.assertEqual('foo_n', e.network_id)
self.assertEqual('foo_r', e.router_id)
self.assertEqual([{'id': 'foo_s'}], e.subnets)
def test__provision_external_connectivity_with_provisioning_error(self):
self.mixin._l3_plugin.create_router.side_effect = Exception
with testtools.ExpectedException(
exceptions.UnknownProvisioningError) as e:
self.mixin._provision_external_connectivity(
self.ctx, 'foo_default',
[{'id': 'foo_s', 'network_id': 'foo_n'}],
'foo_tenant')
self.assertEqual('foo_n', e.network_id)
self.assertIsNone(e.router_id)
self.assertIsNone(e.subnets)
def test__provision_tenant_private_network_with_provisioning_error(self):
self.mixin._core_plugin.create_network.side_effect = Exception
with testtools.ExpectedException(
exceptions.UnknownProvisioningError) as e:
self.mixin._provision_tenant_private_network(
self.ctx, 'foo_tenant')
self.assertIsNone(e.network_id)
def test__check_requirements_fail_on_missing_ext_net(self):
self.assertRaises(exceptions.AutoAllocationFailure,