To allow atomic state changes across Cinder services we need to
implement a way to easily do compare-and-swap.
This patch adds methods to allow compare-and-swap on DB models and on
Cinder Versioned Objects as well.
Conditions for the compare part of the update can consist of:
- Inclusion: status == 'available'
- Exclusion: status != 'in-use'
- Multi-inclusion: status in ('available', 'error')
- Multi-exclusion: status not in ('attaching', 'in-use')
- Sqlalchemy filters
A complete example of usage would be the compare-and-swap used in volume
delete requests that has to take in consideration not only the status
but the attach and migration status as well as the volume not having
snapshots:
now = timeutils.utcnow()
expected = {'attach_status': db.Not('attached'), 'migration_status': None, 'consistencygroup_id': None}
good_status = ('available', 'error', 'error_restoring', 'error_extending')
if not force: expected.update(status=good_status)
# Volume cannot have snapshots if we want to delete it
filters = [~sql.exists().where(models.Volume.id == models.Snapshot.volume_id)]
It can also be specified whether to save already dirtied fields from the
objects or not and by default (if no expected_values argument is
provided) it will make sure that the entry in the DB has the same values
as the objects we are saving.
We can select values based on conditions using Case objects in the
'values' argument. For example:
Exclusion and multi-exclusion will handle, by default, NULL values like
Python does instead of like SQL does, so NULL values will be considered
different than any non NULL values. That way if we search for something
not equal to 1 we will also get NULL values.
WARNING: SQLAlchemy does not allow selecting order of SET clauses, so
for now we cannot do things like {'previous_status': model.status, 'status': 'retyping'}
because it will result in both previous_status and status being set to
'retyping'. Issue has been reported [1] and a patch to fix it [2] has
been submitted.
[1]: https://bitbucket.org/zzzeek/sqlalchemy/issues/3541
[2]: https://github.com/zzzeek/sqlalchemy/pull/200
Reviewed: https:/ /review. openstack. org/205834 /git.openstack. org/cgit/ openstack/ cinder/ commit/ ?id=1070c28774a 74f3e482cb21dbd 98f031010ec4a5
Committed: https:/
Submitter: Jenkins
Branch: master
commit 1070c28774a74f3 e482cb21dbd98f0 31010ec4a5
Author: Gorka Eguileor <email address hidden>
Date: Sat Jul 25 12:34:28 2015 +0200
Add atomic conditional updates to objects
To allow atomic state changes across Cinder services we need to
implement a way to easily do compare-and-swap.
This patch adds methods to allow compare-and-swap on DB models and on
Cinder Versioned Objects as well.
Conditions for the compare part of the update can consist of:
- Inclusion: status == 'available'
- Exclusion: status != 'in-use'
- Multi-inclusion: status in ('available', 'error')
- Multi-exclusion: status not in ('attaching', 'in-use')
- Sqlalchemy filters
A complete example of usage would be the compare-and-swap used in volume
delete requests that has to take in consideration not only the status
but the attach and migration status as well as the volume not having
snapshots:
now = timeutils.utcnow()
'migration_ status' : None,
'consistency group_id' : None}
' error_extending ')
expected. update( status= good_status)
expected = {'attach_status': db.Not('attached'),
good_status = ('available', 'error', 'error_restoring',
if not force:
# Volume cannot have snapshots if we want to delete it ).where( models. Volume. id ==
models. Snapshot. volume_ id)]
filters = [~sql.exists(
updated = vol_obj. conditional_ update(
'previous_ status' : vol_obj. model.status,
'terminated_ at': now},
{'status': 'deleting',
expected,
filters)
It can also be specified whether to save already dirtied fields from the
objects or not and by default (if no expected_values argument is
provided) it will make sure that the entry in the DB has the same values
as the objects we are saving.
We can select values based on conditions using Case objects in the
'values' argument. For example:
has_ snapshot_ filter = sql.exists().where(
models. Snapshot. volume_ id == models.Volume.id) Case([( has_snapshot_ filter, 'has-snapshot')],
else_ ='no-snapshot' ) conditional_ update( {'status' : case_values},
{'status' : 'available'})
case_values = volume.
volume.
Exclusion and multi-exclusion will handle, by default, NULL values like
Python does instead of like SQL does, so NULL values will be considered
different than any non NULL values. That way if we search for something
not equal to 1 we will also get NULL values.
WARNING: SQLAlchemy does not allow selecting order of SET clauses, so
{'previous_ status' : model.status, 'status': 'retyping'} /bitbucket. org/zzzeek/ sqlalchemy/ issues/ 3541 /github. com/zzzeek/ sqlalchemy/ pull/200
for now we cannot do things like
because it will result in both previous_status and status being set to
'retyping'. Issue has been reported [1] and a patch to fix it [2] has
been submitted.
[1]: https:/
[2]: https:/
Specs: https:/ /review. openstack. org/232599/
Implements: blueprint cinder- volume- active- active- support d8fc1f861d52ba8 62875920cdc
Related-Bug: #1490944
Related-Bug: #1238093
Related-Bug: #1490946
Related-Bug: #1469659
Related-Bug: #1493120
Related-Bug: #1493419
Related-Bug: #1493476
Related-Bug: #1494466
Change-Id: If90a37f8c7d6fa