From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
To: Eric Blake <eblake@redhat.com>, qemu-devel@nongnu.org
Cc: jsnow@redhat.com, kwolf@redhat.com, qemu-block@nongnu.org,
Paolo Bonzini <pbonzini@redhat.com>,
Max Reitz <mreitz@redhat.com>,
Markus Armbruster <armbru@redhat.com>
Subject: Re: [Qemu-devel] [PATCH] nbd/client: add x-block-status hack for testing server
Date: Fri, 29 Jun 2018 13:01:50 +0300 [thread overview]
Message-ID: <c4c1dc69-9d64-435b-1dab-c1edfa6ce2bd@virtuozzo.com> (raw)
In-Reply-To: <20180621032539.134944-1-eblake@redhat.com>
Sorry for being late, here are some thoughts. Anyway, idea is good,
we've planned to do something like this, but you were the first)
21.06.2018 06:25, Eric Blake wrote:
> In order to test that the NBD server is properly advertising
> dirty bitmaps, we need a bare minimum client that can request
> and read the context. This patch is a hack (hence the use of
> the x- prefix) that serves two purposes: first, it lets the
> client pass a request of more than one context at a time to
> the server, to test the reaction of the server to various
> contexts (via the list command). Second, whatever the first
> context in the user's list becomes the context wired up to the
> results visible in bdrv_block_status(); this has the result
> that if you pass in 'qemu:dirty-bitmap:b' instead of the usual
> 'base:allocation', and the server is currently serving a named
> bitmap 'b', then commands like 'qemu-img map' now output status
> corresponding to the dirty bitmap (dirty sections look like
> holes, while clean sections look like data, based on how the
> status bits are mapped over the NBD protocol).
>
> Since the hack corrupts the meaning of bdrv_block_status(), I
> would NOT try to run 'qemu-img convert' or any other program
> that might misbehave based on thinking clusters have a different
> status than what the normal 'base:allocation' would provide.
>
> The hack uses a semicolon-separated list embedded in a single
> string, as that was easier to wire into the nbd block driver than
> figuring out the right incantation of flattened QDict to represent
> an array via the command line. Oh well, just one more reason that
> this hack deserves the 'x-' prefix.
>
> As a demo, I was able to prove things work with the following sequence:
>
> $ qemu-img info file
> image: file
> file format: qcow2
> virtual size: 2.0M (2097152 bytes)
> disk size: 2.0M
> cluster_size: 65536
> Format specific information:
> compat: 1.1
> lazy refcounts: false
> refcount bits: 16
> corrupt: false
>
> $ ./x86_64-softmmu/qemu-system-x86_64 -nodefaults -nographic -qmp stdio
> {"QMP": {"version": {"qemu": {"micro": 50, "minor": 12, "major": 2}, "package": "v2.12.0-1531-g3ab98aa673d"}, "capabilities": []}}
> {'execute':'qmp_capabilities'}
> {"return": {}}
> {'execute':'blockdev-add','arguments':{'driver':'qcow2','node-name':'n','file':{'driver':'file','filename':'file'}}}
> {"return": {}}
> {'execute':'block-dirty-bitmap-add','arguments':{'node':'n','name':'b','persistent':true}}
> {"return": {}}
> {'execute':'quit'}
> {"return": {}}
> {"timestamp": {"seconds": 1529548814, "microseconds": 472828}, "event": "SHUTDOWN", "data": {"guest": false}}
>
> $ ./qemu-io -f qcow2 file
> qemu-io> r -v 0 1
> 00000000: 01 .
> read 1/1 bytes at offset 0
> 1 bytes, 1 ops; 0.0001 sec (4.957 KiB/sec and 5076.1421 ops/sec)
> qemu-io> w -P 1 0 1
> wrote 1/1 bytes at offset 0
> 1 bytes, 1 ops; 0.0078 sec (127.502231 bytes/sec and 127.5022 ops/sec)
> qemu-io> q
>
> $ ./x86_64-softmmu/qemu-system-x86_64 -nodefaults -nographic -qmp stdio
> {"QMP": {"version": {"qemu": {"micro": 50, "minor": 12, "major": 2}, "package": "v2.12.0-1531-g3ab98aa673d"}, "capabilities": []}}
> {'execute':'qmp_capabilities'}
> {"return": {}}
> {'execute':'nbd-server-start','arguments':{'addr':{'type':'inet','data':{'host':'localhost','port':'10809'}}}}
> {"return": {}}
> {'execute':'blockdev-add','arguments':{'driver':'qcow2','node-name':'n','file':{'driver':'file','filename':'file'}}}
> {"return": {}}
> {'execute':'nbd-server-add','arguments':{'device':'n'}}
> {"return": {}}
> {'execute':'x-nbd-server-add-bitmap','arguments':{'name':'n','bitmap':'b'}}
> {"error": {"class": "GenericError", "desc": "Bitmap 'b' is enabled"}}
> {'execute':'x-block-dirty-bitmap-disable','arguments':{'node':'n','name':'b'}}
> {"return": {}}
> {'execute':'x-nbd-server-add-bitmap','arguments':{'name':'n','bitmap':'b'}}
> {"return": {}}
> ... leave running
>
> $ ./qemu-img map --output=json --image-opts driver=nbd,export=n,server.type=inet,server.host=localhost,server.port=10809
> [{ "start": 0, "length": 1114112, "depth": 0, "zero": false, "data": true},
> { "start": 1114112, "length": 458752, "depth": 0, "zero": true, "data": false},
> { "start": 1572864, "length": 524288, "depth": 0, "zero": false, "data": true}]
>
> $ ./qemu-img map --output=json --image-opts driver=nbd,export=n,server.type=inet,server.host=localhost,server.port=10809,x-block-status=qemu:dirty-bitmap:b
> [{ "start": 0, "length": 65536, "depth": 0, "zero": false, "data": false},
> { "start": 65536, "length": 2031616, "depth": 0, "zero": false, "data": true}]
>
> The difference between the two runs shows that base:allocation status
> is thus different from the contents of dirty bitmap 'b'; and that the
> dirty bitmap 'b' indeed tracked the first 64k of the file as being
> dirty due to the qemu-io write at offset 0 performed between the creation
> of bitmap b in the first qemu, and the disabling it prior to exporting it
> in the second qemu.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>
> Based-on: <20180621031957.134718-1-eblake@redhat.com>
> ([PULL 0/7] bitmap export over NBD)
>
> qapi/block-core.json | 12 ++++-
> block/nbd-client.h | 1 +
> include/block/nbd.h | 1 +
> block/nbd-client.c | 2 +
> block/nbd.c | 9 +++-
> nbd/client.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++--
> nbd/trace-events | 2 +-
> 7 files changed, 149 insertions(+), 8 deletions(-)
>
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index cc3ede06309..be0456f72b7 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -3484,12 +3484,22 @@
> #
> # @tls-creds: TLS credentials ID
> #
> +# @x-block-status: A string containing a semicolon-separated list of
> +# block status context names to query and then
> +# request
and then request only the first one
> (see NBD_OPT_LIST_META_CONTEXT in the NBD
> +# protocol), only useful for debugging server
> +# behavior. If omitted, no query is made, and the
> +# request uses just "base:allocation". Since this
> +# command is already a hack, it uses a flat string
> +# instead of ['str']. (since 3.0)
> +#
> # Since: 2.9
> ##
> { 'struct': 'BlockdevOptionsNbd',
> 'data': { 'server': 'SocketAddress',
> '*export': 'str',
> - '*tls-creds': 'str' } }
> + '*tls-creds': 'str',
> + '*x-block-status': 'str' } }
>
> ##
> # @BlockdevOptionsRaw:
> diff --git a/block/nbd-client.h b/block/nbd-client.h
> index 0ece76e5aff..18405e84a50 100644
> --- a/block/nbd-client.h
> +++ b/block/nbd-client.h
> @@ -45,6 +45,7 @@ int nbd_client_init(BlockDriverState *bs,
> const char *export_name,
> QCryptoTLSCreds *tlscreds,
> const char *hostname,
> + const char *x_block_status,
> Error **errp);
> void nbd_client_close(BlockDriverState *bs);
>
> diff --git a/include/block/nbd.h b/include/block/nbd.h
> index 8bb9606c39b..79e237e15fa 100644
> --- a/include/block/nbd.h
> +++ b/include/block/nbd.h
> @@ -258,6 +258,7 @@ static inline bool nbd_reply_type_is_error(int type)
> struct NBDExportInfo {
> /* Set by client before nbd_receive_negotiate() */
> bool request_sizes;
> + const char *x_block_status;
>
> /* In-out fields, set by client before nbd_receive_negotiate() and
> * updated by server results during nbd_receive_negotiate() */
> diff --git a/block/nbd-client.c b/block/nbd-client.c
> index 8d69eaaa32f..d27d65d6519 100644
> --- a/block/nbd-client.c
> +++ b/block/nbd-client.c
> @@ -970,6 +970,7 @@ int nbd_client_init(BlockDriverState *bs,
> const char *export,
> QCryptoTLSCreds *tlscreds,
> const char *hostname,
> + const char *x_block_status,
> Error **errp)
> {
> NBDClientSession *client = nbd_get_client_session(bs);
> @@ -982,6 +983,7 @@ int nbd_client_init(BlockDriverState *bs,
> client->info.request_sizes = true;
> client->info.structured_reply = true;
> client->info.base_allocation = true;
> + client->info.x_block_status = x_block_status;
> ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
> tlscreds, hostname,
> &client->ioc, &client->info, errp);
> diff --git a/block/nbd.c b/block/nbd.c
> index 13db4030e67..019d8e05450 100644
> --- a/block/nbd.c
> +++ b/block/nbd.c
> @@ -378,6 +378,11 @@ static QemuOptsList nbd_runtime_opts = {
> .type = QEMU_OPT_STRING,
> .help = "ID of the TLS credentials to use",
> },
> + {
> + .name = "x-block-status",
> + .type = QEMU_OPT_STRING,
> + .help = "debugging only: block status contexts to request",
> + },
> { /* end of list */ }
> },
> };
> @@ -438,8 +443,8 @@ static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
> }
>
> /* NBD handshake */
> - ret = nbd_client_init(bs, sioc, s->export,
> - tlscreds, hostname, errp);
> + ret = nbd_client_init(bs, sioc, s->export, tlscreds, hostname,
> + qemu_opt_get(opts, "x-block-status"), errp);
hm, so, after this nbd_open finish, info.x_block_status will become
invalid pointer..
It's not used in other places, but looks like bad idea anyway. If we
don't want to allocate string,
we can pass it as a separate const char* paramter to nbd_receive_negotiate.
> error:
> if (sioc) {
> object_unref(OBJECT(sioc));
> diff --git a/nbd/client.c b/nbd/client.c
> index 232ff4f46da..f80d88f6c1e 100644
> --- a/nbd/client.c
> +++ b/nbd/client.c
> @@ -601,6 +601,119 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
> return QIO_CHANNEL(tioc);
> }
>
> +/* nbd_negotiate_list_meta_context:
> + * Hack for testing meta context negotiation. Subdivide list on semicolons,
> + * then pass that many queries for info and trace the results.
> + * return 0 for successful negotiation
> + * -1 with errp set for any other error
> + */
> +static int nbd_negotiate_list_meta_context(QIOChannel *ioc,
> + const char *export,
> + const char *context,
> + Error **errp)
> +{
hm, I'd prefer to refactor this, to not duplicate code.. this function
(may be a bit improved) may be called from
nbd_negotiate_simple_meta_context(), and we will test normal code path
when debugging with the hack.
However, I'm ok with this duplication for now, as a debugging hack.
> + int ret;
> + NBDOptionReply reply;
> + uint32_t received_id = 0;
> + char **list = g_strsplit(context, ";", -1);
> + char **iter;
> + uint32_t export_len = strlen(export);
> + uint32_t context_len;
> + uint32_t queries = g_strv_length(list);
> + uint32_t data_len = sizeof(export_len) + export_len +
> + sizeof(queries) + sizeof(context_len) * queries;
> + /* Slight overallocation of data is okay */
> + char *data = g_malloc(data_len + strlen(context));
> + char *p = data;
> +
> + trace_nbd_opt_meta_request("list", context, export);
> + stl_be_p(p, export_len);
> + memcpy(p += sizeof(export_len), export, export_len);
> + stl_be_p(p += export_len, queries);
> + p += sizeof(queries);
> + for (iter = list; *iter; iter++) {
> + context_len = strlen(*iter);
> + stl_be_p(p, context_len);
> + memcpy(p += sizeof(context_len), *iter, context_len);
> + data_len += context_len;
> + p += context_len;
> + }
> +
> + ret = nbd_send_option_request(ioc, NBD_OPT_LIST_META_CONTEXT, data_len,
> + data, errp);
> + g_free(data);
> + if (ret < 0) {
> + goto out;
> + }
> +
> + if (nbd_receive_option_reply(ioc, NBD_OPT_LIST_META_CONTEXT, &reply,
> + errp) < 0)
> + {
> + ret = -1;
> + goto out;
> + }
> +
> + ret = nbd_handle_reply_err(ioc, &reply, errp);
> + if (ret <= 0) {
> + goto out;
> + }
> +
> + while (reply.type == NBD_REP_META_CONTEXT) {
> + char *name;
> +
don't you want to check reply.length before read?
> + if (nbd_read(ioc, &received_id, sizeof(received_id), errp) < 0) {
> + ret = -1;
> + goto out;
> + }
> + be32_to_cpus(&received_id);
> +
> + reply.length -= sizeof(received_id);
> + name = g_malloc(reply.length + 1);
> + if (nbd_read(ioc, name, reply.length, errp) < 0) {
> + g_free(name);
> + ret = -1;
> + goto out;
> + }
> + name[reply.length] = '\0';
> +
> + trace_nbd_opt_meta_reply(name, received_id);
> + g_free(name);
> +
> + /* read next part of reply */
> + if (nbd_receive_option_reply(ioc, NBD_OPT_LIST_META_CONTEXT, &reply,
> + errp) < 0)
> + {
> + ret = -1;
> + goto out;
> + }
> +
> + ret = nbd_handle_reply_err(ioc, &reply, errp);
> + if (ret <= 0) {
> + goto out;
> + }
> + }
> +
> + if (reply.type != NBD_REP_ACK) {
> + error_setg(errp, "Unexpected reply type %" PRIx32 " expected %x",
> + reply.type, NBD_REP_ACK);
> + nbd_send_opt_abort(ioc);
> + ret = -1;
> + goto out;
> + }
> + if (reply.length) {
> + error_setg(errp, "Unexpected length to ACK response");
> + nbd_send_opt_abort(ioc);
> + ret = -1;
> + goto out;
> + }
> +
> + ret = 0;
> +
> + out:
> + g_strfreev(list);
> + return ret;
> +}
> +
> /* nbd_negotiate_simple_meta_context:
> * Set one meta context. Simple means that reply must contain zero (not
> * negotiated) or one (negotiated) contexts. More contexts would be considered
> @@ -622,14 +735,14 @@ static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
> uint32_t received_id = 0;
> bool received = false;
> uint32_t export_len = strlen(export);
> - uint32_t context_len = strlen(context);
> + uint32_t context_len = strchrnul(context, ';') - context;
comment to above this function should be adjusted appropriately
> uint32_t data_len = sizeof(export_len) + export_len +
> sizeof(uint32_t) + /* number of queries */
> sizeof(context_len) + context_len;
> char *data = g_malloc(data_len);
> char *p = data;
>
> - trace_nbd_opt_meta_request(context, export);
> + trace_nbd_opt_meta_request("set", context, export);
> stl_be_p(p, export_len);
> memcpy(p += sizeof(export_len), export, export_len);
> stl_be_p(p += export_len, 1);
> @@ -677,7 +790,7 @@ static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
> return -1;
> }
> name[reply.length] = '\0';
> - if (strcmp(context, name)) {
> + if (strncmp(context, name, context_len)) {
> error_setg(errp, "Failed to negotiate meta context '%s', server "
> "answered with different context '%s'", context,
> name);
> @@ -829,9 +942,18 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
> info->structured_reply = result == 1;
> }
>
> + if (info->structured_reply && info->x_block_status &&
> + nbd_negotiate_list_meta_context(ioc, name,
> + info->x_block_status,
> + errp) < 0) {
> + goto fail;
> + }
> if (info->structured_reply && base_allocation) {
> + if (!info->x_block_status) {
> + info->x_block_status = "base:allocation";
> + }
> result = nbd_negotiate_simple_meta_context(
> - ioc, name, "base:allocation",
> + ioc, name, info->x_block_status,
> &info->meta_base_allocation_id, errp);
> if (result < 0) {
> goto fail;
> diff --git a/nbd/trace-events b/nbd/trace-events
> index 5e1d4afe8e6..7b587a64856 100644
> --- a/nbd/trace-events
> +++ b/nbd/trace-events
> @@ -10,7 +10,7 @@ nbd_receive_query_exports_start(const char *wantname) "Querying export list for
> nbd_receive_query_exports_success(const char *wantname) "Found desired export name '%s'"
> nbd_receive_starttls_new_client(void) "Setting up TLS"
> nbd_receive_starttls_tls_handshake(void) "Starting TLS handshake"
> -nbd_opt_meta_request(const char *context, const char *export) "Requesting to set meta context %s for export %s"
> +nbd_opt_meta_request(const char *act, const char *context, const char *export) "Requesting to %s meta context %s for export %s"
> nbd_opt_meta_reply(const char *context, uint32_t id) "Received mapping of context %s to id %" PRIu32
> nbd_receive_negotiate(void *tlscreds, const char *hostname) "Receiving negotiation tlscreds=%p hostname=%s"
> nbd_receive_negotiate_magic(uint64_t magic) "Magic is 0x%" PRIx64
--
Best regards,
Vladimir
next prev parent reply other threads:[~2018-06-29 10:02 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-06-21 3:25 [Qemu-devel] [PATCH] nbd/client: add x-block-status hack for testing server Eric Blake
2018-06-21 5:45 ` no-reply
2018-06-21 15:09 ` Eric Blake
2018-06-21 5:53 ` no-reply
2018-06-29 15:53 ` Eric Blake
2018-06-25 13:50 ` Max Reitz
2018-06-25 17:12 ` John Snow
2018-06-29 10:01 ` Vladimir Sementsov-Ogievskiy [this message]
2018-06-29 15:55 ` Eric Blake
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=c4c1dc69-9d64-435b-1dab-c1edfa6ce2bd@virtuozzo.com \
--to=vsementsov@virtuozzo.com \
--cc=armbru@redhat.com \
--cc=eblake@redhat.com \
--cc=jsnow@redhat.com \
--cc=kwolf@redhat.com \
--cc=mreitz@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).