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:
parent
512b3c6486
commit
2c48c9e22d
|
@ -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']
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)]
|
||||
|
||||
|
|
Loading…
Reference in New Issue