nbd patches for 2020-10-27

- Tweak the new block-export-add QMP command
 - Allow multiple -B options for qemu-nbd
 - Add qemu:allocation-depth metadata context as qemu-nbd -A
 - Improve iotest use of NBD
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAl+cdhIACgkQp6FrSiUn
 Q2o1uwf+Irf0Gzk3D0UpOuHiq+UDUa8S8TbhL7j0HkArCqIAWlXZOrS1L1yfAXgb
 QMTEkdsalHJebx5BJPzT2NO64gg1tOo3qdlMr/lULoZtSqMuOMkE5LfTPZeEVddf
 vUyqabBl75KIt24Aznw6KsHTkV9UH2FgdY5f840dYgiGvFM8Uzmc+sXlAdiCbFY/
 3BnenL0bux6I8C6t6RM0Yp2gEBOKsf1PvovvrN/GM/Tl6YtEj6YM5yfgy2onGKHU
 j6CDltaOCRCF48z6qf+z7+3lB/yGHx9xSZ8l9f9cDcf3oU7QqG08GdDYznGgIXlK
 gIEu9p3gLcKgintz8+lt9OYOkaAcRA==
 =qhU9
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2020-10-27-v2' into staging

nbd patches for 2020-10-27

- Tweak the new block-export-add QMP command
- Allow multiple -B options for qemu-nbd
- Add qemu:allocation-depth metadata context as qemu-nbd -A
- Improve iotest use of NBD

# gpg: Signature made Fri 30 Oct 2020 20:22:42 GMT
# gpg:                using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full]
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full]
# gpg:                 aka "[jpeg image of size 6874]" [full]
# Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2  F3AA A7A1 6B4A 2527 436A

* remotes/ericb/tags/pull-nbd-2020-10-27-v2:
  nbd: Add 'qemu-nbd -A' to expose allocation depth
  nbd: Add new qemu:allocation-depth metadata context
  block: Return depth level during bdrv_is_allocated_above
  nbd: Allow export of multiple bitmaps for one device
  nbd: Refactor counting of metadata contexts
  nbd: Simplify qemu bitmap context name
  nbd: Update qapi to support exporting multiple bitmaps
  nbd: Utilize QAPI_CLONE for type conversion
  qapi: Add QAPI_LIST_PREPEND() macro
  block: Simplify QAPI_LIST_ADD
  iotests/291: Stop NBD server
  iotests/291: Filter irrelevant parts of img-info

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-11-01 19:05:43 +00:00
commit 6f2ef80b0c
22 changed files with 453 additions and 159 deletions

22
block.c
View File

@ -5220,7 +5220,7 @@ BlockDriverState *bdrv_find_node(const char *node_name)
BlockDeviceInfoList *bdrv_named_nodes_list(bool flat,
Error **errp)
{
BlockDeviceInfoList *list, *entry;
BlockDeviceInfoList *list;
BlockDriverState *bs;
list = NULL;
@ -5230,22 +5230,12 @@ BlockDeviceInfoList *bdrv_named_nodes_list(bool flat,
qapi_free_BlockDeviceInfoList(list);
return NULL;
}
entry = g_malloc0(sizeof(*entry));
entry->value = info;
entry->next = list;
list = entry;
QAPI_LIST_PREPEND(list, info);
}
return list;
}
#define QAPI_LIST_ADD(list, element) do { \
typeof(list) _tmp = g_new(typeof(*(list)), 1); \
_tmp->value = (element); \
_tmp->next = (list); \
(list) = _tmp; \
} while (0)
typedef struct XDbgBlockGraphConstructor {
XDbgBlockGraph *graph;
GHashTable *graph_nodes;
@ -5300,7 +5290,7 @@ static void xdbg_graph_add_node(XDbgBlockGraphConstructor *gr, void *node,
n->type = type;
n->name = g_strdup(name);
QAPI_LIST_ADD(gr->graph->nodes, n);
QAPI_LIST_PREPEND(gr->graph->nodes, n);
}
static void xdbg_graph_add_edge(XDbgBlockGraphConstructor *gr, void *parent,
@ -5319,14 +5309,14 @@ static void xdbg_graph_add_edge(XDbgBlockGraphConstructor *gr, void *parent,
uint64_t flag = bdrv_qapi_perm_to_blk_perm(qapi_perm);
if (flag & child->perm) {
QAPI_LIST_ADD(edge->perm, qapi_perm);
QAPI_LIST_PREPEND(edge->perm, qapi_perm);
}
if (flag & child->shared_perm) {
QAPI_LIST_ADD(edge->shared_perm, qapi_perm);
QAPI_LIST_PREPEND(edge->shared_perm, qapi_perm);
}
}
QAPI_LIST_ADD(gr->graph->edges, edge);
QAPI_LIST_PREPEND(gr->graph->edges, edge);
}

View File

@ -156,7 +156,7 @@ static int coroutine_fn commit_run(Job *job, Error **errp)
/* Copy if allocated above the base */
ret = bdrv_is_allocated_above(blk_bs(s->top), s->base_overlay, true,
offset, COMMIT_BUFFER_SIZE, &n);
copy = (ret == 1);
copy = (ret > 0);
trace_commit_one_iteration(s, offset, n, ret);
if (copy) {
assert(n < SIZE_MAX);

View File

@ -47,7 +47,8 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
int64_t bytes,
int64_t *pnum,
int64_t *map,
BlockDriverState **file);
BlockDriverState **file,
int *depth);
int generated_co_wrapper
bdrv_common_block_status_above(BlockDriverState *bs,
BlockDriverState *base,
@ -57,7 +58,8 @@ bdrv_common_block_status_above(BlockDriverState *bs,
int64_t bytes,
int64_t *pnum,
int64_t *map,
BlockDriverState **file);
BlockDriverState **file,
int *depth);
int coroutine_fn bdrv_co_readv_vmstate(BlockDriverState *bs,
QEMUIOVector *qiov, int64_t pos);

View File

@ -2362,20 +2362,28 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
int64_t bytes,
int64_t *pnum,
int64_t *map,
BlockDriverState **file)
BlockDriverState **file,
int *depth)
{
int ret;
BlockDriverState *p;
int64_t eof = 0;
int dummy;
assert(!include_base || base); /* Can't include NULL base */
if (!depth) {
depth = &dummy;
}
*depth = 0;
if (!include_base && bs == base) {
*pnum = bytes;
return 0;
}
ret = bdrv_co_block_status(bs, want_zero, offset, bytes, pnum, map, file);
++*depth;
if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED || bs == base) {
return ret;
}
@ -2392,6 +2400,7 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
{
ret = bdrv_co_block_status(p, want_zero, offset, bytes, pnum, map,
file);
++*depth;
if (ret < 0) {
return ret;
}
@ -2450,7 +2459,7 @@ int bdrv_block_status_above(BlockDriverState *bs, BlockDriverState *base,
int64_t *map, BlockDriverState **file)
{
return bdrv_common_block_status_above(bs, base, false, true, offset, bytes,
pnum, map, file);
pnum, map, file, NULL);
}
int bdrv_block_status(BlockDriverState *bs, int64_t offset, int64_t bytes,
@ -2478,7 +2487,7 @@ int coroutine_fn bdrv_co_is_zero_fast(BlockDriverState *bs, int64_t offset,
}
ret = bdrv_common_block_status_above(bs, NULL, false, false, offset,
bytes, &pnum, NULL, NULL);
bytes, &pnum, NULL, NULL, NULL);
if (ret < 0) {
return ret;
@ -2495,7 +2504,7 @@ int coroutine_fn bdrv_is_allocated(BlockDriverState *bs, int64_t offset,
ret = bdrv_common_block_status_above(bs, bs, true, false, offset,
bytes, pnum ? pnum : &dummy, NULL,
NULL);
NULL, NULL);
if (ret < 0) {
return ret;
}
@ -2505,8 +2514,9 @@ int coroutine_fn bdrv_is_allocated(BlockDriverState *bs, int64_t offset,
/*
* Given an image chain: ... -> [BASE] -> [INTER1] -> [INTER2] -> [TOP]
*
* Return 1 if (a prefix of) the given range is allocated in any image
* between BASE and TOP (BASE is only included if include_base is set).
* Return a positive depth if (a prefix of) the given range is allocated
* in any image between BASE and TOP (BASE is only included if include_base
* is set). Depth 1 is TOP, 2 is the first backing layer, and so forth.
* BASE can be NULL to check if the given offset is allocated in any
* image of the chain. Return 0 otherwise, or negative errno on
* failure.
@ -2523,13 +2533,18 @@ int bdrv_is_allocated_above(BlockDriverState *top,
bool include_base, int64_t offset,
int64_t bytes, int64_t *pnum)
{
int depth;
int ret = bdrv_common_block_status_above(top, base, include_base, false,
offset, bytes, pnum, NULL, NULL);
offset, bytes, pnum, NULL, NULL,
&depth);
if (ret < 0) {
return ret;
}
return !!(ret & BDRV_BLOCK_ALLOCATED);
if (ret & BDRV_BLOCK_ALLOCATED) {
return depth;
}
return 0;
}
int coroutine_fn

View File

@ -846,7 +846,7 @@ static int coroutine_fn mirror_dirty_init(MirrorBlockJob *s)
}
assert(count);
if (ret == 1) {
if (ret > 0) {
bdrv_set_dirty_bitmap(s->dirty_bitmap, offset, count);
}
offset += count;

View File

@ -135,6 +135,7 @@ typedef struct BDRVNBDState {
QCryptoTLSCreds *tlscreds;
const char *hostname;
char *x_dirty_bitmap;
bool alloc_depth;
bool wait_connect;
NBDConnectThread *connect_thread;
@ -961,6 +962,16 @@ static int nbd_parse_blockstatus_payload(BDRVNBDState *s,
trace_nbd_parse_blockstatus_compliance("extent length too large");
}
/*
* HACK: if we are using x-dirty-bitmaps to access
* qemu:allocation-depth, treat all depths > 2 the same as 2,
* since nbd_client_co_block_status is only expecting the low two
* bits to be set.
*/
if (s->alloc_depth && extent->flags > 2) {
extent->flags = 2;
}
return 0;
}
@ -1795,11 +1806,16 @@ static int nbd_client_handshake(BlockDriverState *bs, QIOChannelSocket *sioc,
s->sioc = NULL;
return ret;
}
if (s->x_dirty_bitmap && !s->info.base_allocation) {
error_setg(errp, "requested x-dirty-bitmap %s not found",
s->x_dirty_bitmap);
ret = -EINVAL;
goto fail;
if (s->x_dirty_bitmap) {
if (!s->info.base_allocation) {
error_setg(errp, "requested x-dirty-bitmap %s not found",
s->x_dirty_bitmap);
ret = -EINVAL;
goto fail;
}
if (strcmp(s->x_dirty_bitmap, "qemu:allocation-depth") == 0) {
s->alloc_depth = true;
}
}
if (s->info.flags & NBD_FLAG_READ_ONLY) {
ret = bdrv_apply_auto_read_only(bs, "NBD export is read-only", errp);

View File

@ -167,7 +167,7 @@ static int coroutine_fn stream_run(Job *job, Error **errp)
n = len - offset;
}
copy = (ret == 1);
copy = (ret > 0);
}
trace_stream_one_iteration(s, offset, n, ret);
if (copy) {

View File

@ -14,6 +14,8 @@
#include "sysemu/block-backend.h"
#include "hw/block/block.h"
#include "qapi/error.h"
#include "qapi/clone-visitor.h"
#include "qapi/qapi-visit-block-export.h"
#include "qapi/qapi-commands-block-export.h"
#include "block/nbd.h"
#include "io/channel-socket.h"
@ -195,7 +197,8 @@ void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
* the device name as a default here for compatibility.
*/
if (!arg->has_name) {
arg->name = arg->device;
arg->has_name = true;
arg->name = g_strdup(arg->device);
}
export_opts = g_new(BlockExportOptions, 1);
@ -205,15 +208,13 @@ void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
.node_name = g_strdup(bdrv_get_node_name(bs)),
.has_writable = arg->has_writable,
.writable = arg->writable,
.u.nbd = {
.has_name = true,
.name = g_strdup(arg->name),
.has_description = arg->has_description,
.description = g_strdup(arg->description),
.has_bitmap = arg->has_bitmap,
.bitmap = g_strdup(arg->bitmap),
},
};
QAPI_CLONE_MEMBERS(BlockExportOptionsNbdBase, &export_opts->u.nbd,
qapi_NbdServerAddOptions_base(arg));
if (arg->has_bitmap) {
export_opts->u.nbd.has_bitmaps = true;
QAPI_LIST_PREPEND(export_opts->u.nbd.bitmaps, g_strdup(arg->bitmap));
}
/*
* nbd-server-add doesn't complain when a read-only device should be

View File

@ -17,19 +17,31 @@ namespace "qemu".
== "qemu" namespace ==
The "qemu" namespace currently contains only one type of context,
related to exposing the contents of a dirty bitmap alongside the
associated disk contents. That context has the following form:
The "qemu" namespace currently contains two available metadata context
types. The first is related to exposing the contents of a dirty
bitmap alongside the associated disk contents. That metadata context
is named with the following form:
qemu:dirty-bitmap:<dirty-bitmap-export-name>
Each dirty-bitmap metadata context defines only one flag for extents
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
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
namespace.
@ -55,3 +67,4 @@ the operation of that feature.
NBD_CMD_BLOCK_STATUS for "qemu:dirty-bitmap:", NBD_CMD_CACHE
* 4.2: NBD_FLAG_CAN_MULTI_CONN for shareable read-only exports,
NBD_CMD_FLAG_FAST_ZERO
* 5.2: NBD_CMD_BLOCK_STATUS for "qemu:allocation-depth"

View File

@ -257,7 +257,8 @@ the 'wait' field, which is only applicable to sockets in server mode
''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Use the more generic commands ``block-export-add`` and ``block-export-del``
instead.
instead. As part of this deprecation, where ``nbd-server-add`` used a
single ``bitmap``, the new ``block-export-add`` uses a list of ``bitmaps``.
Human Monitor Protocol (HMP) commands
-------------------------------------

View File

@ -72,10 +72,16 @@ driver options if ``--image-opts`` is specified.
Export the disk as read-only.
.. option:: -A, --allocation-depth
Expose allocation depth information via the
``qemu:allocation-depth`` metadata context accessible through
NBD_OPT_SET_META_CONTEXT.
.. option:: -B, --bitmap=NAME
If *filename* has a qcow2 persistent bitmap *NAME*, expose
that bitmap via the ``qemu:dirty-bitmap:NAME`` context
that bitmap via the ``qemu:dirty-bitmap:NAME`` metadata context
accessible through NBD_OPT_SET_META_CONTEXT.
.. option:: -s, --snapshot

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>
*
* Network Block Device
@ -47,7 +47,7 @@ typedef struct NBDOptionReply NBDOptionReply;
typedef struct NBDOptionReplyMetaContext {
NBDOptionReply h; /* h.type = NBD_REP_META_CONTEXT, h.length > 4 */
uint32_t context_id;
/* meta context name follows */
/* metadata context name follows */
} QEMU_PACKED NBDOptionReplyMetaContext;
/* Transmission phase structs
@ -229,7 +229,7 @@ enum {
#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
* string.
*/
@ -259,6 +259,8 @@ enum {
/* Extent flags for qemu:dirty-bitmap in NBD_REPLY_TYPE_BLOCK_STATUS */
#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)
{
return type & (1 << 15);

View File

@ -22,4 +22,17 @@ int qapi_enum_parse(const QEnumLookup *lookup, const char *buf,
int parse_qapi_name(const char *name, bool complete);
/*
* For any GenericList @list, insert @element at the front.
*
* Note that this macro evaluates @element exactly once, so it is safe
* to have side-effects with that argument.
*/
#define QAPI_LIST_PREPEND(list, element) do { \
typeof(list) _tmp = g_malloc(sizeof(*(list))); \
_tmp->value = (element); \
_tmp->next = (list); \
(list) = _tmp; \
} while (0)
#endif

View File

@ -27,7 +27,9 @@
#include "qemu/units.h"
#define NBD_META_ID_BASE_ALLOCATION 0
#define NBD_META_ID_DIRTY_BITMAP 1
#define NBD_META_ID_ALLOCATION_DEPTH 1
/* Dirty bitmaps use 'NBD_META_ID_DIRTY_BITMAP + i', so keep this id last. */
#define NBD_META_ID_DIRTY_BITMAP 2
/*
* NBD_MAX_BLOCK_STATUS_EXTENTS: 1 MiB of extents data. An empirical
@ -94,8 +96,9 @@ struct NBDExport {
BlockBackend *eject_notifier_blk;
Notifier eject_notifier;
BdrvDirtyBitmap *export_bitmap;
char *export_bitmap_context;
bool allocation_depth;
BdrvDirtyBitmap **export_bitmaps;
size_t nr_export_bitmaps;
};
static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
@ -105,10 +108,13 @@ static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
* NBD_OPT_LIST_META_CONTEXT. */
typedef struct NBDExportMetaContexts {
NBDExport *exp;
bool valid; /* means that negotiation of the option finished without
errors */
size_t count; /* number of negotiated contexts */
bool base_allocation; /* export base:allocation context (block status) */
bool bitmap; /* export qemu:dirty-bitmap:<export bitmap name> */
bool allocation_depth; /* export qemu:allocation-depth */
bool *bitmaps; /*
* export qemu:dirty-bitmap:<export bitmap name>,
* sized by exp->nr_export_bitmaps
*/
} NBDExportMetaContexts;
struct NBDClient {
@ -446,7 +452,9 @@ static int nbd_negotiate_handle_list(NBDClient *client, Error **errp)
static void nbd_check_meta_export(NBDClient *client)
{
client->export_meta.valid &= client->exp == client->export_meta.exp;
if (client->exp != client->export_meta.exp) {
client->export_meta.count = 0;
}
}
/* Send a reply to NBD_OPT_EXPORT_NAME.
@ -852,11 +860,14 @@ static bool nbd_meta_base_query(NBDClient *client, NBDExportMetaContexts *meta,
/* nbd_meta_qemu_query
*
* 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,
const char *query)
{
size_t i;
if (!nbd_strshift(&query, "qemu:")) {
return false;
}
@ -864,27 +875,44 @@ static bool nbd_meta_qemu_query(NBDClient *client, NBDExportMetaContexts *meta,
if (!*query) {
if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
meta->bitmap = !!meta->exp->export_bitmap;
meta->allocation_depth = meta->exp->allocation_depth;
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
}
trace_nbd_negotiate_meta_query_parse("empty");
return true;
}
if (nbd_strshift(&query, "dirty-bitmap:")) {
trace_nbd_negotiate_meta_query_parse("dirty-bitmap:");
if (!meta->exp->export_bitmap) {
trace_nbd_negotiate_meta_query_skip("no dirty-bitmap exported");
return true;
}
if (nbd_meta_empty_or_pattern(client,
meta->exp->export_bitmap_context +
strlen("qemu:dirty-bitmap:"), query)) {
meta->bitmap = true;
}
if (strcmp(query, "allocation-depth") == 0) {
trace_nbd_negotiate_meta_query_parse("allocation-depth");
meta->allocation_depth = meta->exp->allocation_depth;
return true;
}
trace_nbd_negotiate_meta_query_skip("not dirty-bitmap");
if (nbd_strshift(&query, "dirty-bitmap:")) {
trace_nbd_negotiate_meta_query_parse("dirty-bitmap:");
if (!*query) {
if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
}
trace_nbd_negotiate_meta_query_parse("empty");
return true;
}
for (i = 0; i < meta->exp->nr_export_bitmaps; i++) {
const char *bm_name;
bm_name = bdrv_dirty_bitmap_name(meta->exp->export_bitmaps[i]);
if (strcmp(bm_name, query) == 0) {
meta->bitmaps[i] = true;
trace_nbd_negotiate_meta_query_parse(query);
return true;
}
}
trace_nbd_negotiate_meta_query_skip("no dirty-bitmap match");
return true;
}
trace_nbd_negotiate_meta_query_skip("unknown qemu context");
return true;
}
@ -942,9 +970,11 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
{
int ret;
g_autofree char *export_name = NULL;
NBDExportMetaContexts local_meta;
g_autofree bool *bitmaps = NULL;
NBDExportMetaContexts local_meta = {0};
uint32_t nb_queries;
int i;
size_t i;
size_t count = 0;
if (!client->structured_reply) {
return nbd_opt_invalid(client, errp,
@ -958,6 +988,7 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
meta = &local_meta;
}
g_free(meta->bitmaps);
memset(meta, 0, sizeof(*meta));
ret = nbd_opt_read_name(client, &export_name, NULL, errp);
@ -972,6 +1003,10 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
return nbd_opt_drop(client, NBD_REP_ERR_UNKNOWN, errp,
"export '%s' not present", sane_name);
}
meta->bitmaps = g_new0(bool, meta->exp->nr_export_bitmaps);
if (client->opt == NBD_OPT_LIST_META_CONTEXT) {
bitmaps = meta->bitmaps;
}
ret = nbd_opt_read(client, &nb_queries, sizeof(nb_queries), false, errp);
if (ret <= 0) {
@ -984,7 +1019,8 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
if (client->opt == NBD_OPT_LIST_META_CONTEXT && !nb_queries) {
/* enable all known contexts */
meta->base_allocation = true;
meta->bitmap = !!meta->exp->export_bitmap;
meta->allocation_depth = meta->exp->allocation_depth;
memset(meta->bitmaps, 1, meta->exp->nr_export_bitmaps);
} else {
for (i = 0; i < nb_queries; ++i) {
ret = nbd_negotiate_meta_query(client, meta, errp);
@ -1001,21 +1037,42 @@ static int nbd_negotiate_meta_queries(NBDClient *client,
if (ret < 0) {
return ret;
}
count++;
}
if (meta->bitmap) {
ret = nbd_negotiate_send_meta_context(client,
meta->exp->export_bitmap_context,
NBD_META_ID_DIRTY_BITMAP,
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++) {
const char *bm_name;
g_autofree char *context = NULL;
if (!meta->bitmaps[i]) {
continue;
}
bm_name = bdrv_dirty_bitmap_name(meta->exp->export_bitmaps[i]);
context = g_strdup_printf("qemu:dirty-bitmap:%s", bm_name);
ret = nbd_negotiate_send_meta_context(client, context,
NBD_META_ID_DIRTY_BITMAP + i,
errp);
if (ret < 0) {
return ret;
}
count++;
}
ret = nbd_negotiate_send_rep(client, NBD_REP_ACK, errp);
if (ret == 0) {
meta->valid = true;
meta->count = count;
}
return ret;
@ -1359,6 +1416,7 @@ void nbd_client_put(NBDClient *client)
QTAILQ_REMOVE(&client->exp->clients, client, next);
blk_exp_unref(&client->exp->common);
}
g_free(client->export_meta.bitmaps);
g_free(client);
}
}
@ -1474,6 +1532,8 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
uint64_t perm, shared_perm;
bool readonly = !exp_args->writable;
bool shared = !exp_args->writable;
strList *bitmaps;
size_t i;
int ret;
assert(exp_args->type == BLOCK_EXPORT_TYPE_NBD);
@ -1533,12 +1593,18 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
}
exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);
if (arg->bitmap) {
for (bitmaps = arg->bitmaps; bitmaps; bitmaps = bitmaps->next) {
exp->nr_export_bitmaps++;
}
exp->export_bitmaps = g_new0(BdrvDirtyBitmap *, exp->nr_export_bitmaps);
for (i = 0, bitmaps = arg->bitmaps; bitmaps;
i++, bitmaps = bitmaps->next) {
const char *bitmap = bitmaps->value;
BlockDriverState *bs = blk_bs(blk);
BdrvDirtyBitmap *bm = NULL;
while (bs) {
bm = bdrv_find_dirty_bitmap(bs, arg->bitmap);
bm = bdrv_find_dirty_bitmap(bs, bitmap);
if (bm != NULL) {
break;
}
@ -1548,7 +1614,7 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
if (bm == NULL) {
ret = -ENOENT;
error_setg(errp, "Bitmap '%s' is not found", arg->bitmap);
error_setg(errp, "Bitmap '%s' is not found", bitmap);
goto fail;
}
@ -1562,18 +1628,21 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
ret = -EINVAL;
error_setg(errp,
"Enabled bitmap '%s' incompatible with readonly export",
arg->bitmap);
bitmap);
goto fail;
}
bdrv_dirty_bitmap_set_busy(bm, true);
exp->export_bitmap = bm;
assert(strlen(arg->bitmap) <= BDRV_BITMAP_MAX_NAME_SIZE);
exp->export_bitmap_context = g_strdup_printf("qemu:dirty-bitmap:%s",
arg->bitmap);
assert(strlen(exp->export_bitmap_context) < NBD_MAX_STRING_SIZE);
exp->export_bitmaps[i] = bm;
assert(strlen(bitmap) <= BDRV_BITMAP_MAX_NAME_SIZE);
}
/* Mark bitmaps busy in a separate loop, to simplify roll-back concerns. */
for (i = 0; i < exp->nr_export_bitmaps; i++) {
bdrv_dirty_bitmap_set_busy(exp->export_bitmaps[i], true);
}
exp->allocation_depth = arg->allocation_depth;
blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp);
QTAILQ_INSERT_TAIL(&exports, exp, next);
@ -1581,6 +1650,7 @@ static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
return 0;
fail:
g_free(exp->export_bitmaps);
g_free(exp->name);
g_free(exp->description);
return ret;
@ -1630,6 +1700,7 @@ static void nbd_export_request_shutdown(BlockExport *blk_exp)
static void nbd_export_delete(BlockExport *blk_exp)
{
size_t i;
NBDExport *exp = container_of(blk_exp, NBDExport, common);
assert(exp->name == NULL);
@ -1647,9 +1718,8 @@ static void nbd_export_delete(BlockExport *blk_exp)
blk_aio_detach, exp);
}
if (exp->export_bitmap) {
bdrv_dirty_bitmap_set_busy(exp->export_bitmap, false);
g_free(exp->export_bitmap_context);
for (i = 0; i < exp->nr_export_bitmaps; i++) {
bdrv_dirty_bitmap_set_busy(exp->export_bitmaps[i], false);
}
}
@ -1959,6 +2029,29 @@ static int blockstatus_to_extents(BlockDriverState *bs, uint64_t offset,
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
*
@ -1998,7 +2091,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;
g_autoptr(NBDExtentArray) ea = nbd_extent_array_new(nb_extents);
ret = blockstatus_to_extents(bs, offset, length, ea);
if (context_id == NBD_META_ID_BASE_ALLOCATION) {
ret = blockstatus_to_extents(bs, offset, length, ea);
} else {
ret = blockalloc_to_extents(bs, offset, length, ea);
}
if (ret < 0) {
return nbd_co_send_structured_error(
client, handle, -ret, "can't get block status", errp);
@ -2258,6 +2355,7 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
int flags;
NBDExport *exp = client->exp;
char *msg;
size_t i;
switch (request->type) {
case NBD_CMD_CACHE:
@ -2331,18 +2429,16 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
return nbd_send_generic_reply(client, request->handle, -EINVAL,
"need non-zero length", errp);
}
if (client->export_meta.valid &&
(client->export_meta.base_allocation ||
client->export_meta.bitmap))
{
if (client->export_meta.count) {
bool dont_fragment = request->flags & NBD_CMD_FLAG_REQ_ONE;
int contexts_remaining = client->export_meta.count;
if (client->export_meta.base_allocation) {
ret = nbd_co_send_block_status(client, request->handle,
blk_bs(exp->common.blk),
request->from,
request->len, dont_fragment,
!client->export_meta.bitmap,
!--contexts_remaining,
NBD_META_ID_BASE_ALLOCATION,
errp);
if (ret < 0) {
@ -2350,17 +2446,35 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
}
}
if (client->export_meta.bitmap) {
ret = nbd_co_send_bitmap(client, request->handle,
client->exp->export_bitmap,
request->from, request->len,
dont_fragment,
true, NBD_META_ID_DIRTY_BITMAP, errp);
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++) {
if (!client->export_meta.bitmaps[i]) {
continue;
}
ret = nbd_co_send_bitmap(client, request->handle,
client->exp->export_bitmaps[i],
request->from, request->len,
dont_fragment, !--contexts_remaining,
NBD_META_ID_DIRTY_BITMAP + i, errp);
if (ret < 0) {
return ret;
}
}
assert(!contexts_remaining);
return 0;
} else {
return nbd_send_generic_reply(client, request->handle, -EINVAL,

View File

@ -3905,9 +3905,12 @@
#
# @tls-creds: TLS credentials ID
#
# @x-dirty-bitmap: A "qemu:dirty-bitmap:NAME" string to query in place of
# @x-dirty-bitmap: A metadata context name such as "qemu:dirty-bitmap:NAME"
# or "qemu:allocation-depth" to query in place of the
# traditional "base:allocation" block status (see
# NBD_OPT_LIST_META_CONTEXT in the NBD protocol) (since 3.0)
# NBD_OPT_LIST_META_CONTEXT in the NBD protocol; and
# yes, naming this option x-context would have made
# more sense) (since 3.0)
#
# @reconnect-delay: On an unexpected disconnect, the nbd client tries to
# connect again until succeeding or encountering a serious

View File

@ -63,10 +63,10 @@
'*max-connections': 'uint32' } }
##
# @BlockExportOptionsNbd:
# @BlockExportOptionsNbdBase:
#
# An NBD block export (options shared between nbd-server-add and the NBD branch
# of block-export-add).
# An NBD block export (common options shared between nbd-server-add and
# the NBD branch of block-export-add).
#
# @name: Export name. If unspecified, the @device parameter is used as the
# export name. (Since 2.12)
@ -74,15 +74,32 @@
# @description: Free-form description of the export, up to 4096 bytes.
# (Since 5.0)
#
# @bitmap: Also export the dirty bitmap reachable from @device, so the
# NBD client can use NBD_OPT_SET_META_CONTEXT with
# "qemu:dirty-bitmap:NAME" to inspect the bitmap. (since 4.0)
#
# Since: 5.0
##
{ 'struct': 'BlockExportOptionsNbdBase',
'data': { '*name': 'str', '*description': 'str' } }
##
# @BlockExportOptionsNbd:
#
# An NBD block export (distinct options used in the NBD branch of
# block-export-add).
#
# @bitmaps: Also export each of the named dirty bitmaps reachable from
# @device, so the NBD client can use NBD_OPT_SET_META_CONTEXT with
# the metadata context name "qemu:dirty-bitmap:BITMAP" to inspect
# each bitmap.
#
# @allocation-depth: Also export the allocation depth map for @device, so
# the NBD client can use NBD_OPT_SET_META_CONTEXT with
# the metadata context name "qemu:allocation-depth" to
# inspect allocation details. (since 5.2)
#
# Since: 5.2
##
{ 'struct': 'BlockExportOptionsNbd',
'data': { '*name': 'str', '*description': 'str',
'*bitmap': 'str' } }
'base': 'BlockExportOptionsNbdBase',
'data': { '*bitmaps': ['str'], '*allocation-depth': 'bool' } }
##
# @BlockExportOptionsVhostUserBlk:
@ -106,19 +123,24 @@
##
# @NbdServerAddOptions:
#
# An NBD block export.
# An NBD block export, per legacy nbd-server-add command.
#
# @device: The device name or node name of the node to be exported
#
# @writable: Whether clients should be able to write to the device via the
# NBD connection (default false).
#
# @bitmap: Also export a single dirty bitmap reachable from @device, so the
# NBD client can use NBD_OPT_SET_META_CONTEXT with the metadata
# context name "qemu:dirty-bitmap:BITMAP" to inspect the bitmap
# (since 4.0).
#
# Since: 5.0
##
{ 'struct': 'NbdServerAddOptions',
'base': 'BlockExportOptionsNbd',
'base': 'BlockExportOptionsNbdBase',
'data': { 'device': 'str',
'*writable': 'bool' } }
'*writable': 'bool', '*bitmap': 'str' } }
##
# @nbd-server-add:

View File

@ -100,6 +100,7 @@ static void usage(const char *name)
"\n"
"Exposing part of the image:\n"
" -o, --offset=OFFSET offset into the image\n"
" -A, --allocation-depth expose the allocation depth\n"
" -B, --bitmap=NAME expose a persistent dirty bitmap\n"
"\n"
"General purpose options:\n"
@ -524,7 +525,7 @@ int main(int argc, char **argv)
char *device = NULL;
QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = NULL;
const char *sopt = "hVb:o:p:rsnc:dvk:e:f:tl:x:T:D:B:L";
const char *sopt = "hVb:o:p:rsnc:dvk:e:f:tl:x:T:D:AB:L";
struct option lopt[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
@ -533,6 +534,7 @@ int main(int argc, char **argv)
{ "socket", required_argument, NULL, 'k' },
{ "offset", required_argument, NULL, 'o' },
{ "read-only", no_argument, NULL, 'r' },
{ "allocation-depth", no_argument, NULL, 'A' },
{ "bitmap", required_argument, NULL, 'B' },
{ "connect", required_argument, NULL, 'c' },
{ "disconnect", no_argument, NULL, 'd' },
@ -574,7 +576,8 @@ int main(int argc, char **argv)
QDict *options = NULL;
const char *export_name = NULL; /* defaults to "" later for server mode */
const char *export_description = NULL;
const char *bitmap = NULL;
strList *bitmaps = NULL;
bool alloc_depth = false;
const char *tlscredsid = NULL;
bool imageOpts = false;
bool writethrough = true;
@ -689,8 +692,11 @@ int main(int argc, char **argv)
readonly = true;
flags &= ~BDRV_O_RDWR;
break;
case 'A':
alloc_depth = true;
break;
case 'B':
bitmap = optarg;
QAPI_LIST_PREPEND(bitmaps, g_strdup(optarg));
break;
case 'k':
sockpath = optarg;
@ -786,8 +792,8 @@ int main(int argc, char **argv)
exit(EXIT_FAILURE);
}
if (export_name || export_description || dev_offset ||
device || disconnect || fmt || sn_id_or_name || bitmap ||
seen_aio || seen_discard || seen_cache) {
device || disconnect || fmt || sn_id_or_name || bitmaps ||
alloc_depth || seen_aio || seen_discard || seen_cache) {
error_report("List mode is incompatible with per-device settings");
exit(EXIT_FAILURE);
}
@ -1067,12 +1073,14 @@ int main(int argc, char **argv)
.has_writable = true,
.writable = !readonly,
.u.nbd = {
.has_name = true,
.name = g_strdup(export_name),
.has_description = !!export_description,
.description = g_strdup(export_description),
.has_bitmap = !!bitmap,
.bitmap = g_strdup(bitmap),
.has_name = true,
.name = g_strdup(export_name),
.has_description = !!export_description,
.description = g_strdup(export_description),
.has_bitmaps = !!bitmaps,
.bitmaps = bitmaps,
.has_allocation_depth = alloc_depth,
.allocation_depth = alloc_depth,
},
};
blk_exp_add(export_opts, &error_fatal);

View File

@ -42,6 +42,14 @@ _require_command QEMU_NBD
# compat=0.10 does not support bitmaps
_unsupported_imgopts 'compat=0.10'
# Filter irrelevant format-specific information from the qemu-img info
# output (we only want the bitmaps, basically)
_filter_irrelevant_img_info()
{
grep -v -e 'compat' -e 'compression type' -e 'data file' -e 'extended l2' \
-e 'lazy refcounts' -e 'refcount bits'
}
echo
echo "=== Initial image setup ==="
echo
@ -79,7 +87,7 @@ echo
# Only bitmaps from the active layer are copied
$QEMU_IMG convert --bitmaps -O qcow2 "$TEST_IMG.orig" "$TEST_IMG"
_img_info --format-specific
_img_info --format-specific | _filter_irrelevant_img_info
# But we can also merge in bitmaps from other layers. This test is a bit
# contrived to cover more code paths, in reality, you could merge directly
# into b0 without going through tmp
@ -89,7 +97,7 @@ $QEMU_IMG bitmap --add --merge b0 -b "$TEST_IMG.base" -F $IMGFMT \
$QEMU_IMG bitmap --merge tmp -f $IMGFMT "$TEST_IMG" b0
$QEMU_IMG bitmap --remove --image-opts \
driver=$IMGFMT,file.driver=file,file.filename="$TEST_IMG" tmp
_img_info --format-specific
_img_info --format-specific | _filter_irrelevant_img_info
echo
echo "=== Merge from top layer into backing image ==="
@ -98,7 +106,7 @@ echo
$QEMU_IMG rebase -u -F qcow2 -b "$TEST_IMG.base" "$TEST_IMG"
$QEMU_IMG bitmap --add --merge b2 -b "$TEST_IMG" -F $IMGFMT \
-f $IMGFMT "$TEST_IMG.base" b3
_img_info --format-specific --backing-chain
_img_info --format-specific --backing-chain | _filter_irrelevant_img_info
echo
echo "=== Check bitmap contents ==="
@ -107,19 +115,19 @@ echo
# x-dirty-bitmap is a hack for reading bitmaps; it abuses block status to
# report "data":false for portions of the bitmap which are set
IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
nbd_server_start_unix_socket -r -f qcow2 -B b0 "$TEST_IMG"
nbd_server_start_unix_socket -r -f qcow2 \
-B b0 -B b1 -B b2 -B b3 "$TEST_IMG"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b0" | _filter_qemu_img_map
nbd_server_start_unix_socket -r -f qcow2 -B b1 "$TEST_IMG"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b1" | _filter_qemu_img_map
nbd_server_start_unix_socket -r -f qcow2 -B b2 "$TEST_IMG"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map
nbd_server_start_unix_socket -r -f qcow2 -B b3 "$TEST_IMG"
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b3" | _filter_qemu_img_map
nbd_server_stop
# success, all done
echo '*** done'
rm -f $seq.full

View File

@ -26,9 +26,6 @@ file format: IMGFMT
virtual size: 10 MiB (10485760 bytes)
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
bitmaps:
[0]:
flags:
@ -39,17 +36,12 @@ Format specific information:
[0]: auto
name: b2
granularity: 65536
refcount bits: 16
corrupt: false
extended l2: false
image: TEST_DIR/t.IMGFMT
file format: IMGFMT
virtual size: 10 MiB (10485760 bytes)
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
bitmaps:
[0]:
flags:
@ -64,9 +56,7 @@ Format specific information:
flags:
name: b0
granularity: 65536
refcount bits: 16
corrupt: false
extended l2: false
=== Merge from top layer into backing image ===
@ -77,9 +67,6 @@ cluster_size: 65536
backing file: TEST_DIR/t.IMGFMT.base
backing file format: IMGFMT
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
bitmaps:
[0]:
flags:
@ -94,18 +81,13 @@ Format specific information:
flags:
name: b0
granularity: 65536
refcount bits: 16
corrupt: false
extended l2: false
image: TEST_DIR/t.IMGFMT.base
file format: IMGFMT
virtual size: 10 MiB (10485760 bytes)
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
bitmaps:
[0]:
flags:
@ -117,9 +99,7 @@ Format specific information:
[0]: auto
name: b3
granularity: 65536
refcount bits: 16
corrupt: false
extended l2: false
=== Check bitmap contents ===

77
tests/qemu-iotests/309 Executable file
View File

@ -0,0 +1,77 @@
#!/usr/bin/env bash
#
# Test qemu-nbd -A
#
# Copyright (C) 2018-2020 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
seq="$(basename $0)"
echo "QA output created by $seq"
status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
nbd_server_stop
}
trap "_cleanup; exit \$status" 0 1 2 3 15
# get standard environment, filters and checks
. ./common.rc
. ./common.filter
. ./common.nbd
_supported_fmt qcow2
_supported_proto file
_supported_os Linux
_require_command QEMU_NBD
echo
echo "=== Initial image setup ==="
echo
TEST_IMG="$TEST_IMG.base" _make_test_img 4M
$QEMU_IO -c 'w 0 2M' -f $IMGFMT "$TEST_IMG.base" | _filter_qemu_io
_make_test_img -b "$TEST_IMG.base" -F $IMGFMT 4M
$QEMU_IO -c 'w 1M 2M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
echo
echo "=== Check allocation over NBD ==="
echo
$QEMU_IMG map --output=json -f qcow2 "$TEST_IMG"
IMG="driver=nbd,server.type=unix,server.path=$nbd_unix_socket"
nbd_server_start_unix_socket -r -f qcow2 -A "$TEST_IMG"
# Normal -f raw NBD block status loses access to allocation information
$QEMU_IMG map --output=json --image-opts \
"$IMG" | _filter_qemu_img_map
# But when we use -A, coupled with x-dirty-bitmap in the client for feeding
# 2-bit block status from an alternative NBD metadata context (note that
# the client code for x-dirty-bitmap intentionally collapses all depths
# beyond 2 into a single value), we can determine:
# unallocated (depth 0) => "zero":false, "data":true
# local (depth 1) => "zero":false, "data":false
# backing (depth 2+) => "zero":true, "data":true
$QEMU_IMG map --output=json --image-opts \
"$IMG,x-dirty-bitmap=qemu:allocation-depth" | _filter_qemu_img_map
# More accurate results can be obtained by other NBD clients such as
# libnbd, but this test works without such external dependencies.
# success, all done
echo '*** done'
rm -f $seq.full
status=0

View File

@ -0,0 +1,22 @@
QA output created by 309
=== Initial image setup ===
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=4194304
wrote 2097152/2097152 bytes at offset 0
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT
wrote 2097152/2097152 bytes at offset 1048576
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
=== Check allocation over NBD ===
[{ "start": 0, "length": 1048576, "depth": 1, "zero": false, "data": true, "offset": 327680},
{ "start": 1048576, "length": 2097152, "depth": 0, "zero": false, "data": true, "offset": 327680},
{ "start": 3145728, "length": 1048576, "depth": 1, "zero": true, "data": false}]
[{ "start": 0, "length": 3145728, "depth": 0, "zero": false, "data": true, "offset": OFFSET},
{ "start": 3145728, "length": 1048576, "depth": 0, "zero": true, "data": false, "offset": OFFSET}]
[{ "start": 0, "length": 1048576, "depth": 0, "zero": true, "data": true, "offset": OFFSET},
{ "start": 1048576, "length": 2097152, "depth": 0, "zero": false, "data": false},
{ "start": 3145728, "length": 1048576, "depth": 0, "zero": false, "data": true, "offset": OFFSET}]
*** done

View File

@ -315,3 +315,4 @@
304 rw quick
305 rw quick
307 rw quick export
309 rw auto quick