nbd: Add new qemu:allocation-depth metadata context

'qemu-img map' provides a way to determine which extents of an image
come from the top layer vs. inherited from a backing chain.  This is
useful information worth exposing over NBD.  There is a proposal to
add a QMP command block-dirty-bitmap-populate which can create a dirty
bitmap that reflects allocation information, at which point the
qemu:dirty-bitmap:NAME metadata context can expose that information
via the creation of a temporary bitmap, but we can shorten the effort
by adding a new qemu:allocation-depth metadata context that does the
same thing without an intermediate bitmap (this patch does not
eliminate the need for that proposal, as it will have other uses as
well).

While documenting things, remember that although the NBD protocol has
NBD_OPT_SET_META_CONTEXT, the rest of its documentation refers to
'metadata context', which is a more apt description of what is
actually being used by NBD_CMD_BLOCK_STATUS: the user is requesting
metadata by passing one or more context names.  So I also touched up
some existing wording to prefer the term 'metadata context' where it
makes sense.

Note that this patch does not actually enable any way to request a
server to enable this context; that will come in the next patch.

Signed-off-by: Eric Blake <eblake@redhat.com>
Message-Id: <20201027050556.269064-10-eblake@redhat.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
This commit is contained in:
Eric Blake 2020-10-27 00:05:54 -05:00
parent a92b1b065e
commit 71719cd57f
3 changed files with 89 additions and 12 deletions

View File

@ -17,19 +17,31 @@ namespace "qemu".
== "qemu" namespace == == "qemu" namespace ==
The "qemu" namespace currently contains only one type of context, The "qemu" namespace currently contains two available metadata context
related to exposing the contents of a dirty bitmap alongside the types. The first is related to exposing the contents of a dirty
associated disk contents. That context has the following form: bitmap alongside the associated disk contents. That metadata context
is named with the following form:
qemu:dirty-bitmap:<dirty-bitmap-export-name> qemu:dirty-bitmap:<dirty-bitmap-export-name>
Each dirty-bitmap metadata context defines only one flag for extents Each dirty-bitmap metadata context defines only one flag for extents
in reply for NBD_CMD_BLOCK_STATUS: in reply for NBD_CMD_BLOCK_STATUS:
bit 0: NBD_STATE_DIRTY, means that the extent is "dirty" bit 0: NBD_STATE_DIRTY, set when the extent is "dirty"
The second is related to exposing the source of various extents within
the image, with a single metadata context named:
qemu:allocation-depth
In the allocation depth context, the entire 32-bit value represents a
depth of which layer in a thin-provisioned backing chain provided the
data (0 for unallocated, 1 for the active layer, 2 for the first
backing layer, and so forth).
For NBD_OPT_LIST_META_CONTEXT the following queries are supported For NBD_OPT_LIST_META_CONTEXT the following queries are supported
in addition to "qemu:dirty-bitmap:<dirty-bitmap-export-name>": in addition to the specific "qemu:allocation-depth" and
"qemu:dirty-bitmap:<dirty-bitmap-export-name>":
* "qemu:" - returns list of all available metadata contexts in the * "qemu:" - returns list of all available metadata contexts in the
namespace. namespace.
@ -55,3 +67,4 @@ the operation of that feature.
NBD_CMD_BLOCK_STATUS for "qemu:dirty-bitmap:", NBD_CMD_CACHE NBD_CMD_BLOCK_STATUS for "qemu:dirty-bitmap:", NBD_CMD_CACHE
* 4.2: NBD_FLAG_CAN_MULTI_CONN for shareable read-only exports, * 4.2: NBD_FLAG_CAN_MULTI_CONN for shareable read-only exports,
NBD_CMD_FLAG_FAST_ZERO NBD_CMD_FLAG_FAST_ZERO
* 5.2: NBD_CMD_BLOCK_STATUS for "qemu:allocation-depth"

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016-2019 Red Hat, Inc. * Copyright (C) 2016-2020 Red Hat, Inc.
* Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws> * Copyright (C) 2005 Anthony Liguori <anthony@codemonkey.ws>
* *
* Network Block Device * Network Block Device
@ -47,7 +47,7 @@ typedef struct NBDOptionReply NBDOptionReply;
typedef struct NBDOptionReplyMetaContext { typedef struct NBDOptionReplyMetaContext {
NBDOptionReply h; /* h.type = NBD_REP_META_CONTEXT, h.length > 4 */ NBDOptionReply h; /* h.type = NBD_REP_META_CONTEXT, h.length > 4 */
uint32_t context_id; uint32_t context_id;
/* meta context name follows */ /* metadata context name follows */
} QEMU_PACKED NBDOptionReplyMetaContext; } QEMU_PACKED NBDOptionReplyMetaContext;
/* Transmission phase structs /* Transmission phase structs
@ -229,7 +229,7 @@ enum {
#define NBD_MAX_BUFFER_SIZE (32 * 1024 * 1024) #define NBD_MAX_BUFFER_SIZE (32 * 1024 * 1024)
/* /*
* Maximum size of a protocol string (export name, meta context name, * Maximum size of a protocol string (export name, metadata context name,
* etc.). Use malloc rather than stack allocation for storage of a * etc.). Use malloc rather than stack allocation for storage of a
* string. * string.
*/ */
@ -259,6 +259,8 @@ enum {
/* Extent flags for qemu:dirty-bitmap in NBD_REPLY_TYPE_BLOCK_STATUS */ /* Extent flags for qemu:dirty-bitmap in NBD_REPLY_TYPE_BLOCK_STATUS */
#define NBD_STATE_DIRTY (1 << 0) #define NBD_STATE_DIRTY (1 << 0)
/* No flags needed for qemu:allocation-depth in NBD_REPLY_TYPE_BLOCK_STATUS */
static inline bool nbd_reply_type_is_error(int type) static inline bool nbd_reply_type_is_error(int type)
{ {
return type & (1 << 15); return type & (1 << 15);

View File

@ -27,8 +27,9 @@
#include "qemu/units.h" #include "qemu/units.h"
#define NBD_META_ID_BASE_ALLOCATION 0 #define NBD_META_ID_BASE_ALLOCATION 0
#define NBD_META_ID_ALLOCATION_DEPTH 1
/* Dirty bitmaps use 'NBD_META_ID_DIRTY_BITMAP + i', so keep this id last. */ /* Dirty bitmaps use 'NBD_META_ID_DIRTY_BITMAP + i', so keep this id last. */
#define NBD_META_ID_DIRTY_BITMAP 1 #define NBD_META_ID_DIRTY_BITMAP 2
/* /*
* NBD_MAX_BLOCK_STATUS_EXTENTS: 1 MiB of extents data. An empirical * NBD_MAX_BLOCK_STATUS_EXTENTS: 1 MiB of extents data. An empirical
@ -95,6 +96,7 @@ struct NBDExport {
BlockBackend *eject_notifier_blk; BlockBackend *eject_notifier_blk;
Notifier eject_notifier; Notifier eject_notifier;
bool allocation_depth;
BdrvDirtyBitmap **export_bitmaps; BdrvDirtyBitmap **export_bitmaps;
size_t nr_export_bitmaps; size_t nr_export_bitmaps;
}; };
@ -108,6 +110,7 @@ typedef struct NBDExportMetaContexts {
NBDExport *exp; NBDExport *exp;
size_t count; /* number of negotiated contexts */ size_t count; /* number of negotiated contexts */
bool base_allocation; /* export base:allocation context (block status) */ bool base_allocation; /* export base:allocation context (block status) */
bool allocation_depth; /* export qemu:allocation-depth */
bool *bitmaps; /* bool *bitmaps; /*
* export qemu:dirty-bitmap:<export bitmap name>, * export qemu:dirty-bitmap:<export bitmap name>,
* sized by exp->nr_export_bitmaps * sized by exp->nr_export_bitmaps
@ -857,7 +860,8 @@ static bool nbd_meta_base_query(NBDClient *client, NBDExportMetaContexts *meta,
/* nbd_meta_qemu_query /* nbd_meta_qemu_query
* *
* Handle queries to 'qemu' namespace. For now, only the qemu:dirty-bitmap: * Handle queries to 'qemu' namespace. For now, only the qemu:dirty-bitmap:
* context is available. Return true if @query has been handled. * and qemu:allocation-depth contexts are available. Return true if @query
* has been handled.
*/ */
static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta, static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta,
const char *query) const char *query)
@ -871,12 +875,19 @@ static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta,
if (!*query) { if (!*query) {
if (client->opt == NBD_OPT_LIST_META_CONTEXT) { if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
meta->allocation_depth = meta->exp->allocation_depth;
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps); memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
} }
trace_nbd_negotiate_meta_query_parse("empty"); trace_nbd_negotiate_meta_query_parse("empty");
return true; return true;
} }
if (strcmp(query, "allocation-depth") == 0) {
trace_nbd_negotiate_meta_query_parse("allocation-depth");
meta->allocation_depth = meta->exp->allocation_depth;
return true;
}
if (nbd_strshift(&query, "dirty-bitmap:")) { if (nbd_strshift(&query, "dirty-bitmap:")) {
trace_nbd_negotiate_meta_query_parse("dirty-bitmap:"); trace_nbd_negotiate_meta_query_parse("dirty-bitmap:");
if (!*query) { if (!*query) {
@ -901,7 +912,7 @@ static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta,
return true; return true;
} }
trace_nbd_negotiate_meta_query_skip("not dirty-bitmap"); trace_nbd_negotiate_meta_query_skip("unknown qemu context");
return true; return true;
} }
@ -1008,6 +1019,7 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
if (client->opt == NBD_OPT_LIST_META_CONTEXT && !nb_queries) { if (client->opt == NBD_OPT_LIST_META_CONTEXT && !nb_queries) {
/* enable all known contexts */ /* enable all known contexts */
meta->base_allocation = true; meta->base_allocation = true;
meta->allocation_depth = meta->exp->allocation_depth;
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps); memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
} else { } else {
for (i = 0; i < nb_queries; ++i) { for (i = 0; i < nb_queries; ++i) {
@ -1028,6 +1040,16 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
count++; count++;
} }
if (meta->allocation_depth) {
ret = nbd_negotiate_send_meta_context(client, "qemu:allocation-depth",
NBD_META_ID_ALLOCATION_DEPTH,
errp);
if (ret < 0) {
return ret;
}
count++;
}
for (i = 0; i < meta->exp->nr_export_bitmaps; i++) { for (i = 0; i < meta->exp->nr_export_bitmaps; i++) {
const char *bm_name; const char *bm_name;
g_autofree char *context = NULL; g_autofree char *context = NULL;
@ -2005,6 +2027,29 @@ static int blockstatus_to_extents(BlockDriverState *bs, uint64_t offset,
return 0; return 0;
} }
static int blockalloc_to_extents(BlockDriverState *bs, uint64_t offset,
uint64_t bytes, NBDExtentArray *ea)
{
while (bytes) {
int64_t num;
int ret = bdrv_is_allocated_above(bs, NULL, false, offset, bytes,
&num);
if (ret < 0) {
return ret;
}
if (nbd_extent_array_add(ea, num, ret) < 0) {
return 0;
}
offset += num;
bytes -= num;
}
return 0;
}
/* /*
* nbd_co_send_extents * nbd_co_send_extents
* *
@ -2044,7 +2089,11 @@ static int nbd_co_send_block_status(NBDClient *client, uint64_t handle,
unsigned int nb_extents = dont_fragment ? 1 : NBD_MAX_BLOCK_STATUS_EXTENTS; unsigned int nb_extents = dont_fragment ? 1 : NBD_MAX_BLOCK_STATUS_EXTENTS;
g_autoptr(NBDExtentArray) ea = nbd_extent_array_new(nb_extents); g_autoptr(NBDExtentArray) ea = nbd_extent_array_new(nb_extents);
if (context_id == NBD_META_ID_BASE_ALLOCATION) {
ret = blockstatus_to_extents(bs, offset, length, ea); ret = blockstatus_to_extents(bs, offset, length, ea);
} else {
ret = blockalloc_to_extents(bs, offset, length, ea);
}
if (ret < 0) { if (ret < 0) {
return nbd_co_send_structured_error( return nbd_co_send_structured_error(
client, handle, -ret, "can't get block status", errp); client, handle, -ret, "can't get block status", errp);
@ -2395,6 +2444,19 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
} }
} }
if (client->export_meta.allocation_depth) {
ret = nbd_co_send_block_status(client, request->handle,
blk_bs(exp->common.blk),
request->from, request->len,
dont_fragment,
!--contexts_remaining,
NBD_META_ID_ALLOCATION_DEPTH,
errp);
if (ret < 0) {
return ret;
}
}
for (i = 0; i < client->exp->nr_export_bitmaps; i++) { for (i = 0; i < client->exp->nr_export_bitmaps; i++) {
if (!client->export_meta.bitmaps[i]) { if (!client->export_meta.bitmaps[i]) {
continue; continue;