Replace select-for-update in floating_ip_allocate_address

MySQL Galera does not support the write-intent locks
that SELECT FOR UPDATE requires. In this patch SELECT
FOR UPDATE in 'floating_ip_allocate_address' function
was replaced with SELECT and UPDATE.

The problems with MySQL Galera and algorithm of replacing
SELECT FOR UPDATE relate to spec
https://review.openstack.org/#/c/135296

Related blueprint lock-free-quota-management

Change-Id: I87caf40ce0e42b66623741b27177912497bcb567
This commit is contained in:
pkholkin 2014-12-24 16:39:07 +04:00
parent 512b3c6486
commit 2c48c9e22d
3 changed files with 77 additions and 6 deletions

View File

@ -729,6 +729,9 @@ def floating_ip_get_pools(context):
@require_context
@_retry_on_deadlock
@retrying.retry(stop_max_attempt_number=5, retry_on_exception=
lambda e: isinstance(e, exception.FloatingIpAllocateFailed))
def floating_ip_allocate_address(context, project_id, pool,
auto_assigned=False):
nova.context.authorize_project_context(context, project_id)
@ -739,15 +742,26 @@ def floating_ip_allocate_address(context, project_id, pool,
filter_by(fixed_ip_id=None).\
filter_by(project_id=None).\
filter_by(pool=pool).\
with_lockmode('update').\
first()
# NOTE(vish): if with_lockmode isn't supported, as in sqlite,
# then this has concurrency issues
if not floating_ip_ref:
raise exception.NoMoreFloatingIps()
floating_ip_ref['project_id'] = project_id
floating_ip_ref['auto_assigned'] = auto_assigned
session.add(floating_ip_ref)
params = {'project_id': project_id, 'auto_assigned': auto_assigned}
rows_update = model_query(context, models.FloatingIp,
session=session, read_deleted="no").\
filter_by(id=floating_ip_ref['id']).\
filter_by(fixed_ip_id=None).\
filter_by(project_id=None).\
filter_by(pool=pool).\
update(params, synchronize_session='evaluate')
if not rows_update:
LOG.debug('The row was updated in a concurrent transaction, '
'we will fetch another one')
raise exception.FloatingIpAllocateFailed()
return floating_ip_ref['address']

View File

@ -862,6 +862,10 @@ class NoFloatingIpInterface(NotFound):
msg_fmt = _("Interface %(interface)s not found.")
class FloatingIpAllocateFailed(NovaException):
msg_fmt = _("Floating IP allocate failed.")
class CannotDisassociateAutoAssignedFloatingIP(NovaException):
ec2_code = "UnsupportedOperation"
msg_fmt = _("Cannot disassociate auto assigned floating ip")

View File

@ -4347,6 +4347,59 @@ class FloatingIpTestCase(test.TestCase, ModelsObjectComparatorMixin):
db.floating_ip_allocate_address,
ctxt, 'other_project_id', 'any_pool')
def test_floating_ip_allocate_address_succeeds_retry(self):
pool = 'pool0'
address = '0.0.0.0'
vals = {'pool': pool, 'address': address, 'project_id': None}
floating_ip = self._create_floating_ip(vals)
project_id = self._get_base_values()['project_id']
def fake_first():
if mock_first.call_count == 1:
return {'pool': pool, 'project_id': None, 'fixed_ip_id': None,
'address': address, 'id': 'invalid_id'}
else:
return {'pool': pool, 'project_id': None, 'fixed_ip_id': None,
'address': address, 'id': 1}
with mock.patch('sqlalchemy.orm.query.Query.first',
side_effect=fake_first) as mock_first:
float_addr = db.floating_ip_allocate_address(self.ctxt,
project_id, pool)
self.assertEqual(address, float_addr)
self.assertEqual(2, mock_first.call_count)
float_ip = db.floating_ip_get(self.ctxt, floating_ip.id)
self.assertEqual(project_id, float_ip['project_id'])
def test_floating_ip_allocate_address_retry_limit_exceeded(self):
pool = 'pool0'
address = '0.0.0.0'
vals = {'pool': pool, 'address': address, 'project_id': None}
self._create_floating_ip(vals)
project_id = self._get_base_values()['project_id']
def fake_first():
return {'pool': pool, 'project_id': None, 'fixed_ip_id': None,
'address': address, 'id': 'invalid_id'}
with mock.patch('sqlalchemy.orm.query.Query.first',
side_effect=fake_first) as mock_first:
self.assertRaises(exception.FloatingIpAllocateFailed,
db.floating_ip_allocate_address, self.ctxt,
project_id, pool)
self.assertEqual(5, mock_first.call_count)
def test_floating_ip_allocate_address_no_more_ips_with_no_retries(self):
with mock.patch('sqlalchemy.orm.query.Query.first',
return_value=None) as mock_first:
self.assertRaises(exception.NoMoreFloatingIps,
db.floating_ip_allocate_address,
self.ctxt, 'any_project_id', 'no_such_pool')
self.assertEqual(1, mock_first.call_count)
def _get_existing_ips(self):
return [ip['address'] for ip in db.floating_ip_get_all(self.ctxt)]