Commit c88a1de5 authored by Benoît Canet's avatar Benoît Canet Committed by Kevin Wolf
Browse files

quorum: Add quorum_open() and quorum_close().



Example of command line:

-drive if=virtio,driver=quorum,\
children.0.file.filename=1.raw,\
children.0.node-name=1.raw,\
children.0.driver=raw,\
children.1.file.filename=2.raw,\
children.1.node-name=2.raw,\
children.1.driver=raw,\
children.2.file.filename=3.raw,\
children.2.node-name=3.raw,\
children.2.driver=raw,\
vote-threshold=2

blkverify=on with vote-threshold=2 and two files can be passed to
emulate blkverify.

Signed-off-by: default avatarBenoit Canet <benoit@irqsave.net>
Reviewed-by: default avatarMax Reitz <mreitz@redhat.com>
Signed-off-by: default avatarKevin Wolf <kwolf@redhat.com>
parent 98a7a38f
Loading
Loading
Loading
Loading
+161 −0
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@

#define HASH_LENGTH 32

#define QUORUM_OPT_VOTE_THRESHOLD "vote-threshold"
#define QUORUM_OPT_BLKVERIFY      "blkverify"

/* This union holds a vote hash value */
typedef union QuorumVoteValue {
    char h[HASH_LENGTH];       /* SHA-256 hash */
@@ -672,12 +675,170 @@ static bool quorum_recurse_is_first_non_filter(BlockDriverState *bs,
    return false;
}

static int quorum_valid_threshold(int threshold, int num_children, Error **errp)
{

    if (threshold < 1) {
        error_set(errp, QERR_INVALID_PARAMETER_VALUE,
                  "vote-threshold", "value >= 1");
        return -ERANGE;
    }

    if (threshold > num_children) {
        error_setg(errp, "threshold may not exceed children count");
        return -ERANGE;
    }

    return 0;
}

static QemuOptsList quorum_runtime_opts = {
    .name = "quorum",
    .head = QTAILQ_HEAD_INITIALIZER(quorum_runtime_opts.head),
    .desc = {
        {
            .name = QUORUM_OPT_VOTE_THRESHOLD,
            .type = QEMU_OPT_NUMBER,
            .help = "The number of vote needed for reaching quorum",
        },
        {
            .name = QUORUM_OPT_BLKVERIFY,
            .type = QEMU_OPT_BOOL,
            .help = "Trigger block verify mode if set",
        },
        { /* end of list */ }
    },
};

static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
                       Error **errp)
{
    BDRVQuorumState *s = bs->opaque;
    Error *local_err = NULL;
    QemuOpts *opts;
    bool *opened;
    QDict *sub = NULL;
    QList *list = NULL;
    const QListEntry *lentry;
    const QDictEntry *dentry;
    int i;
    int ret = 0;

    qdict_flatten(options);
    qdict_extract_subqdict(options, &sub, "children.");
    qdict_array_split(sub, &list);

    /* count how many different children are present and validate
     * qdict_size(sub) address the open by reference case
     */
    s->num_children = !qlist_size(list) ? qdict_size(sub) : qlist_size(list);
    if (s->num_children < 2) {
        error_setg(&local_err,
                   "Number of provided children must be greater than 1");
        ret = -EINVAL;
        goto exit;
    }

    opts = qemu_opts_create(&quorum_runtime_opts, NULL, 0, &error_abort);
    qemu_opts_absorb_qdict(opts, options, &local_err);
    if (error_is_set(&local_err)) {
        ret = -EINVAL;
        goto exit;
    }

    s->threshold = qemu_opt_get_number(opts, QUORUM_OPT_VOTE_THRESHOLD, 0);

    /* and validate it against s->num_children */
    ret = quorum_valid_threshold(s->threshold, s->num_children, &local_err);
    if (ret < 0) {
        goto exit;
    }

    /* is the driver in blkverify mode */
    if (qemu_opt_get_bool(opts, QUORUM_OPT_BLKVERIFY, false) &&
        s->num_children == 2 && s->threshold == 2) {
        s->is_blkverify = true;
    } else if (qemu_opt_get_bool(opts, QUORUM_OPT_BLKVERIFY, false)) {
        fprintf(stderr, "blkverify mode is set by setting blkverify=on "
                "and using two files with vote_threshold=2\n");
    }

    /* allocate the children BlockDriverState array */
    s->bs = g_new0(BlockDriverState *, s->num_children);
    opened = g_new0(bool, s->num_children);

    /* Open by file name or options dict (command line or QMP) */
    if (s->num_children == qlist_size(list)) {
        for (i = 0, lentry = qlist_first(list); lentry;
            lentry = qlist_next(lentry), i++) {
            QDict *d = qobject_to_qdict(lentry->value);
            QINCREF(d);
            ret = bdrv_open(&s->bs[i], NULL, NULL, d, flags, NULL, &local_err);
            if (ret < 0) {
                goto close_exit;
            }
            opened[i] = true;
        }
    /* Open by QMP references */
    } else {
        for (i = 0, dentry = qdict_first(sub); dentry;
             dentry = qdict_next(sub, dentry), i++) {
            QString *string = qobject_to_qstring(dentry->value);
            ret = bdrv_open(&s->bs[i], NULL, qstring_get_str(string), NULL,
                            flags, NULL, &local_err);
            if (ret < 0) {
                goto close_exit;
            }
            opened[i] = true;
        }
    }

    g_free(opened);
    goto exit;

close_exit:
    /* cleanup on error */
    for (i = 0; i < s->num_children; i++) {
        if (!opened[i]) {
            continue;
        }
        bdrv_unref(s->bs[i]);
    }
    g_free(s->bs);
    g_free(opened);
exit:
    /* propagate error */
    if (error_is_set(&local_err)) {
        error_propagate(errp, local_err);
    }
    QDECREF(list);
    QDECREF(sub);
    return ret;
}

static void quorum_close(BlockDriverState *bs)
{
    BDRVQuorumState *s = bs->opaque;
    int i;

    for (i = 0; i < s->num_children; i++) {
        bdrv_unref(s->bs[i]);
    }

    g_free(s->bs);
}

static BlockDriver bdrv_quorum = {
    .format_name        = "quorum",
    .protocol_name      = "quorum",

    .instance_size      = sizeof(BDRVQuorumState),

    .bdrv_file_open     = quorum_open,
    .bdrv_close         = quorum_close,

    .authorizations     = { true, true },

    .bdrv_co_flush_to_disk = quorum_co_flush,

    .bdrv_getlength     = quorum_getlength,
+3 −0
Original line number Diff line number Diff line
@@ -640,6 +640,9 @@ static void monitor_protocol_event_init(void)
    monitor_protocol_event_throttle(QEVENT_RTC_CHANGE, 1000);
    monitor_protocol_event_throttle(QEVENT_BALLOON_CHANGE, 1000);
    monitor_protocol_event_throttle(QEVENT_WATCHDOG, 1000);
    /* limit the rate of quorum events to avoid hammering the management */
    monitor_protocol_event_throttle(QEVENT_QUORUM_REPORT_BAD, 1000);
    monitor_protocol_event_throttle(QEVENT_QUORUM_FAILURE, 1000);
}

/**
+20 −1
Original line number Diff line number Diff line
@@ -4431,6 +4431,24 @@
  'data': { 'test': 'BlockdevRef',
            'raw': 'BlockdevRef' } }

##
# @BlockdevOptionsQuorum
#
# Driver specific block device options for Quorum
#
# @blkverify:      #optional true if the driver must print content mismatch
#
# @children:       the children block device to use
#
# @vote_threshold: the vote limit under which a read will fail
#
# Since: 2.0
##
{ 'type': 'BlockdevOptionsQuorum',
  'data': { '*blkverify': 'bool',
            'children': [ 'BlockdevRef' ],
            'vote-threshold': 'int' } }

##
# @BlockdevOptions
#
@@ -4470,7 +4488,8 @@
      'vdi':        'BlockdevOptionsGenericFormat',
      'vhdx':       'BlockdevOptionsGenericFormat',
      'vmdk':       'BlockdevOptionsGenericCOWFormat',
      'vpc':        'BlockdevOptionsGenericFormat'
      'vpc':        'BlockdevOptionsGenericFormat',
      'quorum':     'BlockdevOptionsQuorum'
  } }

##