Merge "Fix MySQL DB error in Delete CG"

This commit is contained in:
Jenkins 2016-06-06 19:41:30 +00:00 committed by Gerrit Code Review
commit 2cc0749631
3 changed files with 88 additions and 8 deletions

View File

@ -4235,13 +4235,23 @@ def cg_has_volumes_filter(attached_or_with_snapshots=False):
def cg_creating_from_src(cg_id=None, cgsnapshot_id=None):
model = aliased(models.ConsistencyGroup)
conditions = [~model.deleted, model.status == 'creating']
# NOTE(geguileo): As explained in devref api_conditional_updates we use a
# subquery to trick MySQL into using the same table in the update and the
# where clause.
subq = sql.select([models.ConsistencyGroup]).where(
and_(~models.ConsistencyGroup.deleted,
models.ConsistencyGroup.status == 'creating')).alias('cg2')
if cg_id:
conditions.append(model.source_cgid == cg_id)
if cgsnapshot_id:
conditions.append(model.cgsnapshot_id == cgsnapshot_id)
return sql.exists().where(and_(*conditions))
match_id = subq.c.source_cgid == cg_id
elif cgsnapshot_id:
match_id = subq.c.cgsnapshot_id == cgsnapshot_id
else:
msg = _('cg_creating_from_src must be called with cg_id or '
'cgsnapshot_id parameter.')
raise exception.ProgrammingError(reason=msg)
return sql.exists([subq]).where(match_id)
###############################

View File

@ -466,6 +466,29 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
consistencygroup.destroy()
cg2.destroy()
def test_delete_consistencygroup_available_used_as_source_success(self):
consistencygroup = self._create_consistencygroup(
status=fields.ConsistencyGroupStatus.AVAILABLE)
req = webob.Request.blank('/v2/%s/consistencygroups/%s/delete' %
(fake.PROJECT_ID, consistencygroup.id))
# The other CG used the first CG as source, but it's no longer in
# creating status, so we should be able to delete it.
cg2 = self._create_consistencygroup(
status=fields.ConsistencyGroupStatus.AVAILABLE,
source_cgid=consistencygroup.id)
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = jsonutils.dump_as_bytes({})
res = req.get_response(fakes.wsgi_app())
consistencygroup = objects.ConsistencyGroup.get_by_id(
self.ctxt, consistencygroup.id)
self.assertEqual(202, res.status_int)
self.assertEqual('deleting', consistencygroup.status)
consistencygroup.destroy()
cg2.destroy()
def test_delete_consistencygroup_available_no_force(self):
consistencygroup = self._create_consistencygroup(status='available')
req = webob.Request.blank('/v2/%s/consistencygroups/%s/delete' %

View File

@ -347,12 +347,59 @@ Limitations
-----------
We can only use functionality that works on **all** supported DBs, and that's
why we don't allow multi table updates and will raise DBError exception even
when the code is running against a DB engine that supports this functionality.
why we don't allow multi table updates and will raise ProgrammingError
exception even when the code is running against a DB engine that supports this
functionality.
This way we make sure that we don't inadvertently add a multi table update that
works on MySQL but will surely fail on PostgreSQL.
MySQL DB engine also has some limitations that we should be aware of when
creating our filters.
One that is very common is when we are trying to check if there is a row that
matches a specific criteria in the same table that we are updating. For
example, when deleting a Consistency Group we want to check that it is not
being used as the source for a Consistency Group that is in the process of
being created.
The straightforward way of doing this is using the core exists expression and
use an alias to differentiate general query fields and the exists subquery.
Code would look like this:
.. code:: python
def cg_creating_from_src(cg_id):
model = aliased(models.ConsistencyGroup)
return sql.exists().where(and_(
~model.deleted,
model.status == 'creating',
conditions.append(model.source_cgid == cg_id))
While this will work in SQLite and PostgreSQL, it will not work on MySQL and an
error will be raised when the query is executed: "You can't specify target
table 'consistencygroups' for update in FROM clause".
To solve this we have 2 options:
- Create a specific query for MySQL using a feature only available in MySQL,
which is an update with a left self join.
- Use a trick -using a select subquery- that will work on all DBs.
Considering that it's always better to have only 1 way of doing things and that
SQLAlchemy doesn't support MySQL's non standard behavior we should generate
these filters using the select subquery method like this:
.. code:: python
def cg_creating_from_src(cg_id):
subq = sql.select([models.ConsistencyGroup]).where(and_(
~model.deleted,
model.status == 'creating')).alias('cg2')
return sql.exists([subq]).where(subq.c.source_cgid == cgid)
Considerations for new ORM & Versioned Objects
----------------------------------------------