'Compare-and-swap update.
A conditional object update that, unlike normal update, will SAVE the
contents of the update to the DB.
Update will only occur in the DB and the object if conditions are met.
If no expected_values are passed in we will default to make sure that
all fields have not been changed in the DB. Since we cannot know the
original value in the DB for dirty fields in the object those will be
excluded.
We have 4 different condition types we can use in expected_values:
- Equality:  {\'status\': \'available\'}
- Inequality: {\'status\': vol_obj.Not(\'deleting\')}
- In range: {\'status\': [\'available\', \'error\']
- Not in range: {\'status\': vol_obj.Not([\'in-use\', \'attaching\'])
Method accepts additional filters, which are basically anything that
can be passed to a sqlalchemy query\'s filter method, for example:
.. code-block:: python
[~sql.exists().where(models.Volume.id == models.Snapshot.volume_id)]
We can select values based on conditions using Case objects in the
\'values\' argument. For example:
.. code-block:: python
has_snapshot_filter = sql.exists().where(
models.Snapshot.volume_id == models.Volume.id)
case_values = volume.Case([(has_snapshot_filter, \'has-snapshot\')],
else_=\'no-snapshot\')
volume.conditional_update({\'status\': case_values},
{\'status\': \'available\'}))
And we can use DB fields using model class attribute for example to
store previous status in the corresponding field even though we don\'t
know which value is in the db from those we allowed:
.. code-block:: python
volume.conditional_update({\'status\': \'deleting\',
\'previous_status\': volume.model.status},
{\'status\': (\'available\', \'error\')})
:param values: Dictionary of key-values to update in the DB.
:param expected_values: Dictionary of conditions that must be met for
the update to be executed.
:param filters: Iterable with additional filters
:param save_all: Object may have changes that are not in the DB, this
will say whether we want those changes saved as well.
:param session: Session to use for the update
:param reflect_changes: If we want changes made in the database to be
reflected in the versioned object.  This may
mean in some cases that we have to reload the
object from the database.
:param order: Specific order of fields in which to update the values
:returns: number of db rows that were updated, which can be used as a
boolean, since it will be 0 if we couldn\'t update the DB and
1 if we could, because we are using unique index id.'

def conditional_update(self, values, expected_values=None, filters=(), save_all=False, session=None, reflect_changes=True, order=None):

    if ('id' not in self.fields):
        msg = (_('VersionedObject    %s    does    not    support    conditional    update.') % self.obj_name())
        raise NotImplementedError(msg)
    if (expected_values is None):
        changes = self.obj_what_changed()
        expected = {key: getattr(self, key) for key in self.fields.keys() if (self.obj_attr_is_set(key) and (key not in changes) and (key not in self.OPTIONAL_FIELDS))}
    else:
        expected = expected_values.copy()
        expected['id'] = self.id
    if save_all:
        changes = self.cinder_obj_get_changes()
        changes.update(values)
        values = changes
    result = db.conditional_update(self._context, self.model, values, expected, filters, order=order)
    if (result and reflect_changes):
        if any(((isinstance(v, self.Case) or db.is_orm_value(v)) for v in values.values())):
            obj = type(self).get_by_id(self._context, self.id)
            db_values = obj.obj_to_primitive()['versioned_object.data']
            values = {field: db_values[field] for (field, value) in values.items()}
        for (key, value) in values.items():
            setattr(self, key, value)
        self.obj_reset_changes(values.keys())
    return result
