Ping -- any consensus on how we should implement the "do-or-die"
argument for transactions that start block jobs? :)

This patch may look a little hokey in how it boxes arguments, but I can
re-do it on top of Eric Blake's very official way of boxing arguments,
when the QAPI dust settles.

--js

On 09/24/2015 05:40 PM, John Snow wrote:
> This replaces the per-action property as in Fam's series.
> Instead, we have a transaction-wide property that is shared
> with each action.
> 
> At the action level, if a property supplied transaction-wide
> is disagreeable, we return error and the transaction is aborted.
> 
> RFC:
> 
> Where this makes sense: Any transactional actions that aren't
> prepared to accept this new paradigm of transactional behavior
> can error_setg and return.
> 
> Where this may not make sense: consider the transactions which
> do not use BlockJobs to perform their actions, i.e. they are
> synchronous during the transactional phase. Because they either
> fail or succeed so early, we might say that of course they can
> support this property ...
> 
> ...however, consider the case where we create a new bitmap and
> perform a full backup, using allow_partial=false. If the backup
> fails, we might well expect the bitmap to be deleted because a
> member of the transaction ultimately/eventually failed. However,
> the bitmap creation was not undone because it does not have a
> pending/delayed abort/commit action -- those are only for jobs
> in this implementation.
> 
> How do we fix this?
> 
> (1) We just say "No, you can't use the new block job transaction
>     completion mechanic in conjunction with these commands,"
> 
> (2) We make Bitmap creation/resetting small, synchronous blockjobs
>     that can join the BlockJobTxn
> 
> Signed-off-by: John Snow <js...@redhat.com>
> ---
>  blockdev.c             | 87 
> ++++++++++++++++++++++++++++++++++++--------------
>  blockjob.c             |  2 +-
>  qapi-schema.json       | 15 +++++++--
>  qapi/block-core.json   | 26 ---------------
>  qmp-commands.hx        |  2 +-
>  tests/qemu-iotests/124 | 12 +++----
>  6 files changed, 83 insertions(+), 61 deletions(-)
> 
> diff --git a/blockdev.c b/blockdev.c
> index 45a9fe7..02b1a83 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1061,7 +1061,7 @@ static void blockdev_do_action(int kind, void *data, 
> Error **errp)
>      action.data = data;
>      list.value = &action;
>      list.next = NULL;
> -    qmp_transaction(&list, errp);
> +    qmp_transaction(&list, false, NULL, errp);
>  }
>  
>  void qmp_blockdev_snapshot_sync(bool has_device, const char *device,
> @@ -1286,6 +1286,7 @@ struct BlkActionState {
>      TransactionAction *action;
>      const BlkActionOps *ops;
>      BlockJobTxn *block_job_txn;
> +    TransactionProperties *txn_props;
>      QSIMPLEQ_ENTRY(BlkActionState) entry;
>  };
>  
> @@ -1322,6 +1323,12 @@ static void internal_snapshot_prepare(BlkActionState 
> *common,
>      name = internal->name;
>  
>      /* 2. check for validation */
> +    if (!common->txn_props->allow_partial) {
> +        error_setg(errp,
> +                   "internal_snapshot does not support allow_partial = 
> false");
> +        return;
> +    }
> +
>      blk = blk_by_name(device);
>      if (!blk) {
>          error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
> @@ -1473,6 +1480,12 @@ static void external_snapshot_prepare(BlkActionState 
> *common,
>      }
>  
>      /* start processing */
> +    if (!common->txn_props->allow_partial) {
> +        error_setg(errp,
> +                   "external_snapshot does not support allow_partial = 
> false");
> +        return;
> +    }
> +
>      state->old_bs = bdrv_lookup_bs(has_device ? device : NULL,
>                                     has_node_name ? node_name : NULL,
>                                     &local_err);
> @@ -1603,14 +1616,11 @@ static void drive_backup_prepare(BlkActionState 
> *common, Error **errp)
>      DriveBackupState *state = DO_UPCAST(DriveBackupState, common, common);
>      BlockDriverState *bs;
>      BlockBackend *blk;
> -    DriveBackupTxn *backup_txn;
>      DriveBackup *backup;
> -    BlockJobTxn *txn = NULL;
>      Error *local_err = NULL;
>  
>      assert(common->action->kind == TRANSACTION_ACTION_KIND_DRIVE_BACKUP);
> -    backup_txn = common->action->drive_backup;
> -    backup = backup_txn->base;
> +    backup = common->action->drive_backup->base;
>  
>      blk = blk_by_name(backup->device);
>      if (!blk) {
> @@ -1624,11 +1634,6 @@ static void drive_backup_prepare(BlkActionState 
> *common, Error **errp)
>      state->aio_context = bdrv_get_aio_context(bs);
>      aio_context_acquire(state->aio_context);
>  
> -    if (backup_txn->has_transactional_cancel &&
> -        backup_txn->transactional_cancel) {
> -        txn = common->block_job_txn;
> -    }
> -
>      do_drive_backup(backup->device, backup->target,
>                      backup->has_format, backup->format,
>                      backup->sync,
> @@ -1637,7 +1642,7 @@ static void drive_backup_prepare(BlkActionState 
> *common, Error **errp)
>                      backup->has_bitmap, backup->bitmap,
>                      backup->has_on_source_error, backup->on_source_error,
>                      backup->has_on_target_error, backup->on_target_error,
> -                    txn, &local_err);
> +                    common->block_job_txn, &local_err);
>      if (local_err) {
>          error_propagate(errp, local_err);
>          return;
> @@ -1686,16 +1691,13 @@ static void do_blockdev_backup(const char *device, 
> const char *target,
>  static void blockdev_backup_prepare(BlkActionState *common, Error **errp)
>  {
>      BlockdevBackupState *state = DO_UPCAST(BlockdevBackupState, common, 
> common);
> -    BlockdevBackupTxn *backup_txn;
>      BlockdevBackup *backup;
>      BlockDriverState *bs, *target;
>      BlockBackend *blk;
> -    BlockJobTxn *txn = NULL;
>      Error *local_err = NULL;
>  
>      assert(common->action->kind == TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP);
> -    backup_txn = common->action->blockdev_backup;
> -    backup = backup_txn->base;
> +    backup = common->action->blockdev_backup->base;
>  
>      blk = blk_by_name(backup->device);
>      if (!blk) {
> @@ -1720,17 +1722,12 @@ static void blockdev_backup_prepare(BlkActionState 
> *common, Error **errp)
>      }
>      aio_context_acquire(state->aio_context);
>  
> -    if (backup_txn->has_transactional_cancel &&
> -        backup_txn->transactional_cancel) {
> -        txn = common->block_job_txn;
> -    }
> -
>      do_blockdev_backup(backup->device, backup->target,
>                         backup->sync,
>                         backup->has_speed, backup->speed,
>                         backup->has_on_source_error, backup->on_source_error,
>                         backup->has_on_target_error, backup->on_target_error,
> -                       txn, &local_err);
> +                       common->block_job_txn, &local_err);
>      if (local_err) {
>          error_propagate(errp, local_err);
>          return;
> @@ -1777,6 +1774,13 @@ static void 
> block_dirty_bitmap_add_prepare(BlkActionState *common,
>      BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
>                                               common, common);
>  
> +    if (!common->txn_props->allow_partial) {
> +        error_setg(errp,
> +                   "block_dirty_bitmap_add does not support "
> +                   "allow_partial = false");
> +        return;
> +    }
> +
>      action = common->action->block_dirty_bitmap_add;
>      /* AIO context taken and released within qmp_block_dirty_bitmap_add */
>      qmp_block_dirty_bitmap_add(action->node, action->name,
> @@ -1812,6 +1816,13 @@ static void 
> block_dirty_bitmap_clear_prepare(BlkActionState *common,
>                                               common, common);
>      BlockDirtyBitmap *action;
>  
> +    if (!common->txn_props->allow_partial) {
> +        error_setg(errp,
> +                   "block_dirty_bitmap_clear does not support "
> +                   "allow_partial = false");
> +        return;
> +    }
> +
>      action = common->action->block_dirty_bitmap_clear;
>      state->bitmap = block_dirty_bitmap_lookup(action->node,
>                                                action->name,
> @@ -1914,21 +1925,45 @@ static const BlkActionOps actions[] = {
>      }
>  };
>  
> +/**
> + * Allocate a TransactionProperties structure if necessary, and fill
> + * that structure with desired defaults if they are unset.
> + */
> +static TransactionProperties 
> *get_transaction_properties(TransactionProperties *props)
> +{
> +    if (!props) {
> +        props = g_new0(TransactionProperties, 1);
> +    }
> +
> +    if (!props->has_allow_partial) {
> +        props->allow_partial = true;
> +    }
> +
> +    return props;
> +}
> +
>  /*
>   * 'Atomic' group operations.  The operations are performed as a set, and if
>   * any fail then we roll back all operations in the group.
>   */
> -void qmp_transaction(TransactionActionList *dev_list, Error **errp)
> +void qmp_transaction(TransactionActionList *dev_list,
> +                     bool hasTransactionProperties,
> +                     struct TransactionProperties *props,
> +                     Error **errp)
>  {
>      TransactionActionList *dev_entry = dev_list;
> -    BlockJobTxn *block_job_txn;
> +    BlockJobTxn *block_job_txn = NULL;
>      BlkActionState *state, *next;
>      Error *local_err = NULL;
>  
>      QSIMPLEQ_HEAD(snap_bdrv_states, BlkActionState) snap_bdrv_states;
>      QSIMPLEQ_INIT(&snap_bdrv_states);
>  
> -    block_job_txn = block_job_txn_new();
> +    /* Does this transaction get *canceled* as a group on failure? */
> +    props = get_transaction_properties(props);
> +    if (props->allow_partial == false) {
> +        block_job_txn = block_job_txn_new();
> +    }
>  
>      /* drain all i/o before any operations */
>      bdrv_drain_all();
> @@ -1950,6 +1985,7 @@ void qmp_transaction(TransactionActionList *dev_list, 
> Error **errp)
>          state->ops = ops;
>          state->action = dev_info;
>          state->block_job_txn = block_job_txn;
> +        state->txn_props = props;
>          QSIMPLEQ_INSERT_TAIL(&snap_bdrv_states, state, entry);
>  
>          state->ops->prepare(state, &local_err);
> @@ -1982,6 +2018,9 @@ exit:
>          }
>          g_free(state);
>      }
> +    if (!hasTransactionProperties) {
> +        g_free(props);
> +    }
>      block_job_txn_unref(block_job_txn);
>  }
>  
> diff --git a/blockjob.c b/blockjob.c
> index 91e8d3c..00146ff 100644
> --- a/blockjob.c
> +++ b/blockjob.c
> @@ -512,7 +512,7 @@ static void block_job_txn_ref(BlockJobTxn *txn)
>  
>  void block_job_txn_unref(BlockJobTxn *txn)
>  {
> -    if (--txn->refcnt == 0) {
> +    if (txn && --txn->refcnt == 0) {
>          g_free(txn);
>      }
>  }
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 8769099..0f28311 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -1505,14 +1505,20 @@
>  { 'union': 'TransactionAction',
>    'data': {
>         'blockdev-snapshot-sync': 'BlockdevSnapshot',
> -       'drive-backup': 'DriveBackupTxn',
> -       'blockdev-backup': 'BlockdevBackupTxn',
> +       'drive-backup': 'DriveBackup',
> +       'blockdev-backup': 'BlockdevBackup',
>         'abort': 'Abort',
>         'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
>         'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
>         'block-dirty-bitmap-clear': 'BlockDirtyBitmap'
>     } }
>  
> +{ 'struct': 'TransactionProperties',
> +  'data': {
> +      '*allow-partial': 'bool'
> +  }
> +}
> +
>  ##
>  # @transaction
>  #
> @@ -1533,7 +1539,10 @@
>  # Since 1.1
>  ##
>  { 'command': 'transaction',
> -  'data': { 'actions': [ 'TransactionAction' ] } }
> +  'data': { 'actions': [ 'TransactionAction' ],
> +            '*properties': 'TransactionProperties'
> +          }
> +}
>  
>  ##
>  # @human-monitor-command:
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 24db5c2..83742ab 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -749,19 +749,6 @@
>              '*on-target-error': 'BlockdevOnError' } }
>  
>  ##
> -# @DriveBackupTxn
> -#
> -# @transactional-cancel: #optional whether failure or cancellation of other
> -#                        block jobs with @transactional-cancel true in the 
> same
> -#                        transaction causes the whole group to cancel. 
> Default
> -#                        is false.
> -#
> -# Since: 2.5
> -##
> -{ 'struct': 'DriveBackupTxn', 'base': 'DriveBackup',
> -  'data': {'*transactional-cancel': 'bool' } }
> -
> -##
>  # @BlockdevBackup
>  #
>  # @device: the name of the device which should be copied.
> @@ -797,19 +784,6 @@
>              '*on-target-error': 'BlockdevOnError' } }
>  
>  ##
> -# @BlockdevBackupTxn
> -#
> -# @transactional-cancel: #optional whether failure or cancellation of other
> -#                        block jobs with @transactional-cancel true in the 
> same
> -#                        transaction causes the whole group to cancel. 
> Default
> -#                        is false
> -#
> -# Since: 2.5
> -##
> -{ 'struct': 'BlockdevBackupTxn', 'base': 'BlockdevBackup',
> -  'data': {'*transactional-cancel': 'bool' } }
> -
> -##
>  # @blockdev-snapshot-sync
>  #
>  # Generates a synchronous snapshot of a block device.
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index c388274..7c1fed9 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -1262,7 +1262,7 @@ EQMP
>      },
>      {
>          .name       = "transaction",
> -        .args_type  = "actions:q",
> +        .args_type  = "actions:q,properties:q?",
>          .mhandler.cmd_new = qmp_marshal_transaction,
>      },
>  
> diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
> index 33d00ef..028b346 100644
> --- a/tests/qemu-iotests/124
> +++ b/tests/qemu-iotests/124
> @@ -456,14 +456,13 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>          transaction = [
>              transaction_drive_backup(drive0['id'], target0, 
> sync='incremental',
>                                       format=drive0['fmt'], mode='existing',
> -                                     bitmap=dr0bm0.name,
> -                                     transactional_cancel=True),
> +                                     bitmap=dr0bm0.name),
>              transaction_drive_backup(drive1['id'], target1, 
> sync='incremental',
>                                       format=drive1['fmt'], mode='existing',
> -                                     bitmap=dr1bm0.name,
> -                                     transactional_cancel=True),
> +                                     bitmap=dr1bm0.name)
>          ]
> -        result = self.vm.qmp('transaction', actions=transaction)
> +        result = self.vm.qmp('transaction', actions=transaction,
> +                             properties={'allow-partial':False} )
>          self.assert_qmp(result, 'return', {})
>  
>          # Observe that drive0's backup is cancelled and drive1 completes with
> @@ -485,7 +484,8 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>          target1 = self.prepare_backup(dr1bm0)
>  
>          # Re-run the exact same transaction.
> -        result = self.vm.qmp('transaction', actions=transaction)
> +        result = self.vm.qmp('transaction', actions=transaction,
> +                             properties={'allow-partial':False})
>          self.assert_qmp(result, 'return', {})
>  
>          # Both should complete successfully this time.
> 



Reply via email to