qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH] nbd/client: add x-block-status hack for testing ser


From: Eric Blake
Subject: [Qemu-devel] [PATCH] nbd/client: add x-block-status hack for testing server
Date: Wed, 20 Jun 2018 22:25:39 -0500

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 <address@hidden>
---

Based-on: <address@hidden>
([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 (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);
  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)
+{
+    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;
+
+        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;
     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
-- 
2.14.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]