Comment 4 for bug 1469659

Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Related fix merged to cinder (master)

Reviewed: https://review.openstack.org/205834
Committed: https://git.openstack.org/cgit/openstack/cinder/commit/?id=1070c28774a74f3e482cb21dbd98f031010ec4a5
Submitter: Jenkins
Branch: master

commit 1070c28774a74f3e482cb21dbd98f031010ec4a5
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()
     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)]

     updated = vol_obj.conditional_update(
         {'status': 'deleting',
          'previous_status': vol_obj.model.status,
          'terminated_at': now},
         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_values = volume.Case([(has_snapshot_filter, 'has-snapshot')],
                               else_='no-snapshot')
     volume.conditional_update({'status': case_values},
                               {'status': 'available'})

    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

    Specs: https://review.openstack.org/232599/

    Implements: blueprint cinder-volume-active-active-support
    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: If90a37f8c7d6fad8fc1f861d52ba862875920cdc