Block layer patches:

- Add block export infrastructure
 - iotests improvements
 - Document the throttle block filter
 - Misc code cleanups
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAl93OwURHGt3b2xmQHJl
 ZGhhdC5jb20ACgkQfwmycsiPL9Z6EA/+P5nqjxkhZy8CMj3nNh7d8pLN7JElRxkm
 Ie/nNwlHtCa1C2PO/Y3IDM8UZgRqKKiZGZEzRiYVEtvssF+hpf7aL1U09Y8V/zXH
 LGxEIzAV2uZ5ig5VIR9yBvMYufN1p0v2J0iMVcFyVxzMAAGKGq6vVx8IX/O6mggB
 e0w/jdoMsPwKY+egb4QEOZGu/Ae+rLDos6ka+uq8BBppa6FY6jfkkm87ogZfWu+i
 HRCifAKjX419KSH+1qC1r2hEHXmnbNRFEacPpbPjSjkTIPLtQcSBCQfS+U7jUlCy
 hVEgl0AEbduqhWy8ckBRHuPGPjGcTgh0jBv1btXsbDDbrPsLTdX2FZK2P5qqUSod
 nk6nngBq1ULgTHYjsReR5Q8y6DM5Or3vn58fLGb0tSymrpl8DAIt2zLlOhqat2uF
 L89nryn7imZ6GdNb4ZUg5yf/2p2gJQ8jcPrO9p7KNNWdIbIbZ52SjTSLgtscaKft
 zJ13oZNskBt3Ts4PuWxxeqn4+l+Y0/YdWm58pSSWzt7xbRt/QDxPAgvvgJNZK/XG
 xhZSNRRLdwnXrj5tc/cVl2jsKoSxzX8smGmmFUFbUFMLCX8SE7lK2THO1bPLfdUQ
 SYlkmrFYVEwk9lRlTY7zVjRYEst+J5mwP7XISOn7dU23BGy99NmbC231g0a4c9vM
 x66nUYdVWbw=
 =VIZ3
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging

Block layer patches:

- Add block export infrastructure
- iotests improvements
- Document the throttle block filter
- Misc code cleanups

# gpg: Signature made Fri 02 Oct 2020 15:36:53 BST
# gpg:                using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6
# gpg:                issuer "kwolf@redhat.com"
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full]
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74  56FE 7F09 B272 C88F 2FD6

* remotes/kevin/tags/for-upstream: (37 commits)
  qcow2: Use L1E_SIZE in qcow2_write_l1_entry()
  qemu-storage-daemon: Fix help line for --export
  iotests: Test block-export-* QMP interface
  iotests: Allow supported and unsupported formats at the same time
  iotests: Introduce qemu_nbd_list_log()
  iotests: Factor out qemu_tool_pipe_and_status()
  nbd: Deprecate nbd-server-add/remove
  nbd: Merge nbd_export_new() and nbd_export_create()
  block/export: Move writable to BlockExportOptions
  block/export: Add query-block-exports
  block/export: Create BlockBackend in blk_exp_add()
  block/export: Move blk to BlockExport
  block/export: Add BLOCK_EXPORT_DELETED event
  block/export: Add block-export-del
  block/export: Move strong user reference to block_exports
  block/export: Add 'id' option to block-export-add
  block/export: Add blk_exp_close_all(_type)
  block/export: Allocate BlockExport in blk_exp_add()
  block/export: Add node-name to BlockExportOptions
  block/export: Move AioContext from NBDExport to BlockExport
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-10-02 16:19:42 +01:00
commit 469e72ab7d
30 changed files with 1434 additions and 551 deletions

View File

@ -4462,7 +4462,7 @@ static void bdrv_close(BlockDriverState *bs)
void bdrv_close_all(void) void bdrv_close_all(void)
{ {
assert(job_next(NULL) == NULL); assert(job_next(NULL) == NULL);
nbd_export_close_all(); blk_exp_close_all();
/* Drop references from requests still in flight, such as canceled block /* Drop references from requests still in flight, such as canceled block
* jobs whose AIO context has not been polled yet */ * jobs whose AIO context has not been polled yet */

325
block/export/export.c Normal file
View File

@ -0,0 +1,325 @@
/*
* Common block export infrastructure
*
* Copyright (c) 2012, 2020 Red Hat, Inc.
*
* Authors:
* Paolo Bonzini <pbonzini@redhat.com>
* Kevin Wolf <kwolf@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or
* later. See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "block/block.h"
#include "sysemu/block-backend.h"
#include "block/export.h"
#include "block/nbd.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-block-export.h"
#include "qapi/qapi-events-block-export.h"
#include "qemu/id.h"
static const BlockExportDriver *blk_exp_drivers[] = {
&blk_exp_nbd,
};
/* Only accessed from the main thread */
static QLIST_HEAD(, BlockExport) block_exports =
QLIST_HEAD_INITIALIZER(block_exports);
BlockExport *blk_exp_find(const char *id)
{
BlockExport *exp;
QLIST_FOREACH(exp, &block_exports, next) {
if (strcmp(id, exp->id) == 0) {
return exp;
}
}
return NULL;
}
static const BlockExportDriver *blk_exp_find_driver(BlockExportType type)
{
int i;
for (i = 0; i < ARRAY_SIZE(blk_exp_drivers); i++) {
if (blk_exp_drivers[i]->type == type) {
return blk_exp_drivers[i];
}
}
return NULL;
}
BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
{
const BlockExportDriver *drv;
BlockExport *exp = NULL;
BlockDriverState *bs;
BlockBackend *blk;
AioContext *ctx;
uint64_t perm;
int ret;
if (!id_wellformed(export->id)) {
error_setg(errp, "Invalid block export id");
return NULL;
}
if (blk_exp_find(export->id)) {
error_setg(errp, "Block export id '%s' is already in use", export->id);
return NULL;
}
drv = blk_exp_find_driver(export->type);
if (!drv) {
error_setg(errp, "No driver found for the requested export type");
return NULL;
}
bs = bdrv_lookup_bs(NULL, export->node_name, errp);
if (!bs) {
return NULL;
}
if (!export->has_writable) {
export->writable = false;
}
if (bdrv_is_read_only(bs) && export->writable) {
error_setg(errp, "Cannot export read-only node as writable");
return NULL;
}
ctx = bdrv_get_aio_context(bs);
aio_context_acquire(ctx);
/*
* Block exports are used for non-shared storage migration. Make sure
* that BDRV_O_INACTIVE is cleared and the image is ready for write
* access since the export could be available before migration handover.
* ctx was acquired in the caller.
*/
bdrv_invalidate_cache(bs, NULL);
perm = BLK_PERM_CONSISTENT_READ;
if (export->writable) {
perm |= BLK_PERM_WRITE;
}
blk = blk_new(ctx, perm, BLK_PERM_ALL);
ret = blk_insert_bs(blk, bs, errp);
if (ret < 0) {
goto fail;
}
if (!export->has_writethrough) {
export->writethrough = false;
}
blk_set_enable_write_cache(blk, !export->writethrough);
assert(drv->instance_size >= sizeof(BlockExport));
exp = g_malloc0(drv->instance_size);
*exp = (BlockExport) {
.drv = drv,
.refcount = 1,
.user_owned = true,
.id = g_strdup(export->id),
.ctx = ctx,
.blk = blk,
};
ret = drv->create(exp, export, errp);
if (ret < 0) {
goto fail;
}
assert(exp->blk != NULL);
QLIST_INSERT_HEAD(&block_exports, exp, next);
aio_context_release(ctx);
return exp;
fail:
blk_unref(blk);
aio_context_release(ctx);
if (exp) {
g_free(exp->id);
g_free(exp);
}
return NULL;
}
/* Callers must hold exp->ctx lock */
void blk_exp_ref(BlockExport *exp)
{
assert(exp->refcount > 0);
exp->refcount++;
}
/* Runs in the main thread */
static void blk_exp_delete_bh(void *opaque)
{
BlockExport *exp = opaque;
AioContext *aio_context = exp->ctx;
aio_context_acquire(aio_context);
assert(exp->refcount == 0);
QLIST_REMOVE(exp, next);
exp->drv->delete(exp);
blk_unref(exp->blk);
qapi_event_send_block_export_deleted(exp->id);
g_free(exp->id);
g_free(exp);
aio_context_release(aio_context);
}
/* Callers must hold exp->ctx lock */
void blk_exp_unref(BlockExport *exp)
{
assert(exp->refcount > 0);
if (--exp->refcount == 0) {
/* Touch the block_exports list only in the main thread */
aio_bh_schedule_oneshot(qemu_get_aio_context(), blk_exp_delete_bh,
exp);
}
}
/*
* Drops the user reference to the export and requests that all client
* connections and other internally held references start to shut down. When
* the function returns, there may still be active references while the export
* is in the process of shutting down.
*
* Acquires exp->ctx internally. Callers must *not* hold the lock.
*/
void blk_exp_request_shutdown(BlockExport *exp)
{
AioContext *aio_context = exp->ctx;
aio_context_acquire(aio_context);
/*
* If the user doesn't own the export any more, it is already shutting
* down. We must not call .request_shutdown and decrease the refcount a
* second time.
*/
if (!exp->user_owned) {
goto out;
}
exp->drv->request_shutdown(exp);
assert(exp->user_owned);
exp->user_owned = false;
blk_exp_unref(exp);
out:
aio_context_release(aio_context);
}
/*
* Returns whether a block export of the given type exists.
* type == BLOCK_EXPORT_TYPE__MAX checks for an export of any type.
*/
static bool blk_exp_has_type(BlockExportType type)
{
BlockExport *exp;
if (type == BLOCK_EXPORT_TYPE__MAX) {
return !QLIST_EMPTY(&block_exports);
}
QLIST_FOREACH(exp, &block_exports, next) {
if (exp->drv->type == type) {
return true;
}
}
return false;
}
/* type == BLOCK_EXPORT_TYPE__MAX for all types */
void blk_exp_close_all_type(BlockExportType type)
{
BlockExport *exp, *next;
assert(in_aio_context_home_thread(qemu_get_aio_context()));
QLIST_FOREACH_SAFE(exp, &block_exports, next, next) {
if (type != BLOCK_EXPORT_TYPE__MAX && exp->drv->type != type) {
continue;
}
blk_exp_request_shutdown(exp);
}
AIO_WAIT_WHILE(NULL, blk_exp_has_type(type));
}
void blk_exp_close_all(void)
{
blk_exp_close_all_type(BLOCK_EXPORT_TYPE__MAX);
}
void qmp_block_export_add(BlockExportOptions *export, Error **errp)
{
blk_exp_add(export, errp);
}
void qmp_block_export_del(const char *id,
bool has_mode, BlockExportRemoveMode mode,
Error **errp)
{
ERRP_GUARD();
BlockExport *exp;
exp = blk_exp_find(id);
if (exp == NULL) {
error_setg(errp, "Export '%s' is not found", id);
return;
}
if (!exp->user_owned) {
error_setg(errp, "Export '%s' is already shutting down", id);
return;
}
if (!has_mode) {
mode = BLOCK_EXPORT_REMOVE_MODE_SAFE;
}
if (mode == BLOCK_EXPORT_REMOVE_MODE_SAFE && exp->refcount > 1) {
error_setg(errp, "export '%s' still in use", exp->id);
error_append_hint(errp, "Use mode='hard' to force client "
"disconnect\n");
return;
}
blk_exp_request_shutdown(exp);
}
BlockExportInfoList *qmp_query_block_exports(Error **errp)
{
BlockExportInfoList *head = NULL, **p_next = &head;
BlockExport *exp;
QLIST_FOREACH(exp, &block_exports, next) {
BlockExportInfoList *entry = g_new0(BlockExportInfoList, 1);
BlockExportInfo *info = g_new(BlockExportInfo, 1);
*info = (BlockExportInfo) {
.id = g_strdup(exp->id),
.type = exp->drv->type,
.node_name = g_strdup(bdrv_get_node_name(blk_bs(exp->blk))),
.shutting_down = !exp->user_owned,
};
entry->value = info;
*p_next = entry;
p_next = &entry->next;
}
return head;
}

1
block/export/meson.build Normal file
View File

@ -0,0 +1 @@
block_ss.add(files('export.c'))

View File

@ -110,6 +110,8 @@ block_ss.add(module_block_h)
block_ss.add(files('stream.c')) block_ss.add(files('stream.c'))
softmmu_ss.add(files('qapi-sysemu.c')) softmmu_ss.add(files('qapi-sysemu.c'))
subdir('export')
subdir('monitor') subdir('monitor')
modules += {'block': block_modules} modules += {'block': block_modules}

View File

@ -40,6 +40,7 @@
#include "sysemu/block-backend.h" #include "sysemu/block-backend.h"
#include "sysemu/blockdev.h" #include "sysemu/blockdev.h"
#include "qapi/qapi-commands-block.h" #include "qapi/qapi-commands-block.h"
#include "qapi/qapi-commands-block-export.h"
#include "qapi/qmp/qdict.h" #include "qapi/qmp/qdict.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qapi/qmp/qerror.h" #include "qapi/qmp/qerror.h"
@ -397,7 +398,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
Error *local_err = NULL; Error *local_err = NULL;
BlockInfoList *block_list, *info; BlockInfoList *block_list, *info;
SocketAddress *addr; SocketAddress *addr;
BlockExportNbd export; NbdServerAddOptions export;
if (writable && !all) { if (writable && !all) {
error_setg(&local_err, "-w only valid together with -a"); error_setg(&local_err, "-w only valid together with -a");
@ -410,7 +411,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
goto exit; goto exit;
} }
nbd_server_start(addr, NULL, NULL, &local_err); nbd_server_start(addr, NULL, NULL, 0, &local_err);
qapi_free_SocketAddress(addr); qapi_free_SocketAddress(addr);
if (local_err != NULL) { if (local_err != NULL) {
goto exit; goto exit;
@ -430,7 +431,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
continue; continue;
} }
export = (BlockExportNbd) { export = (NbdServerAddOptions) {
.device = info->value->device, .device = info->value->device,
.has_writable = true, .has_writable = true,
.writable = writable, .writable = writable,
@ -457,7 +458,7 @@ void hmp_nbd_server_add(Monitor *mon, const QDict *qdict)
bool writable = qdict_get_try_bool(qdict, "writable", false); bool writable = qdict_get_try_bool(qdict, "writable", false);
Error *local_err = NULL; Error *local_err = NULL;
BlockExportNbd export = { NbdServerAddOptions export = {
.device = (char *) device, .device = (char *) device,
.has_name = !!name, .has_name = !!name,
.name = (char *) name, .name = (char *) name,
@ -475,8 +476,8 @@ void hmp_nbd_server_remove(Monitor *mon, const QDict *qdict)
bool force = qdict_get_try_bool(qdict, "force", false); bool force = qdict_get_try_bool(qdict, "force", false);
Error *err = NULL; Error *err = NULL;
/* Rely on NBD_SERVER_REMOVE_MODE_SAFE being the default */ /* Rely on BLOCK_EXPORT_REMOVE_MODE_SAFE being the default */
qmp_nbd_server_remove(name, force, NBD_SERVER_REMOVE_MODE_HARD, &err); qmp_nbd_server_remove(name, force, BLOCK_EXPORT_REMOVE_MODE_HARD, &err);
hmp_handle_error(mon, err); hmp_handle_error(mon, err);
} }

View File

@ -240,14 +240,14 @@ int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index)
} }
ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_ACTIVE_L1, ret = qcow2_pre_write_overlap_check(bs, QCOW2_OL_ACTIVE_L1,
s->l1_table_offset + 8 * l1_start_index, bufsize, false); s->l1_table_offset + L1E_SIZE * l1_start_index, bufsize, false);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
BLKDBG_EVENT(bs->file, BLKDBG_L1_UPDATE); BLKDBG_EVENT(bs->file, BLKDBG_L1_UPDATE);
ret = bdrv_pwrite_sync(bs->file, ret = bdrv_pwrite_sync(bs->file,
s->l1_table_offset + 8 * l1_start_index, s->l1_table_offset + L1E_SIZE * l1_start_index,
buf, bufsize); buf, bufsize);
if (ret < 0) { if (ret < 0) {
return ret; return ret;

View File

@ -740,7 +740,7 @@ static coroutine_fn void reconnect_to_sdog(void *opaque)
if (s->fd < 0) { if (s->fd < 0) {
trace_sheepdog_reconnect_to_sdog(); trace_sheepdog_reconnect_to_sdog();
error_report_err(local_err); error_report_err(local_err);
qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 1000000000ULL); qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, NANOSECONDS_PER_SECOND);
} }
}; };

View File

@ -14,7 +14,7 @@
#include "sysemu/block-backend.h" #include "sysemu/block-backend.h"
#include "hw/block/block.h" #include "hw/block/block.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qapi/qapi-commands-block.h" #include "qapi/qapi-commands-block-export.h"
#include "block/nbd.h" #include "block/nbd.h"
#include "io/channel-socket.h" #include "io/channel-socket.h"
#include "io/net-listener.h" #include "io/net-listener.h"
@ -23,23 +23,52 @@ typedef struct NBDServerData {
QIONetListener *listener; QIONetListener *listener;
QCryptoTLSCreds *tlscreds; QCryptoTLSCreds *tlscreds;
char *tlsauthz; char *tlsauthz;
uint32_t max_connections;
uint32_t connections;
} NBDServerData; } NBDServerData;
static NBDServerData *nbd_server; static NBDServerData *nbd_server;
static bool is_qemu_nbd;
static void nbd_update_server_watch(NBDServerData *s);
void nbd_server_is_qemu_nbd(bool value)
{
is_qemu_nbd = value;
}
bool nbd_server_is_running(void)
{
return nbd_server || is_qemu_nbd;
}
static void nbd_blockdev_client_closed(NBDClient *client, bool ignored) static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
{ {
nbd_client_put(client); nbd_client_put(client);
assert(nbd_server->connections > 0);
nbd_server->connections--;
nbd_update_server_watch(nbd_server);
} }
static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc, static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
gpointer opaque) gpointer opaque)
{ {
nbd_server->connections++;
nbd_update_server_watch(nbd_server);
qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server"); qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz, nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz,
nbd_blockdev_client_closed); nbd_blockdev_client_closed);
} }
static void nbd_update_server_watch(NBDServerData *s)
{
if (!s->max_connections || s->connections < s->max_connections) {
qio_net_listener_set_client_func(s->listener, nbd_accept, NULL, NULL);
} else {
qio_net_listener_set_client_func(s->listener, NULL, NULL, NULL);
}
}
static void nbd_server_free(NBDServerData *server) static void nbd_server_free(NBDServerData *server)
{ {
@ -88,7 +117,8 @@ static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
void nbd_server_start(SocketAddress *addr, const char *tls_creds, void nbd_server_start(SocketAddress *addr, const char *tls_creds,
const char *tls_authz, Error **errp) const char *tls_authz, uint32_t max_connections,
Error **errp)
{ {
if (nbd_server) { if (nbd_server) {
error_setg(errp, "NBD server already running"); error_setg(errp, "NBD server already running");
@ -96,6 +126,7 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
} }
nbd_server = g_new0(NBDServerData, 1); nbd_server = g_new0(NBDServerData, 1);
nbd_server->max_connections = max_connections;
nbd_server->listener = qio_net_listener_new(); nbd_server->listener = qio_net_listener_new();
qio_net_listener_set_name(nbd_server->listener, qio_net_listener_set_name(nbd_server->listener,
@ -120,10 +151,7 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
nbd_server->tlsauthz = g_strdup(tls_authz); nbd_server->tlsauthz = g_strdup(tls_authz);
qio_net_listener_set_client_func(nbd_server->listener, nbd_update_server_watch(nbd_server);
nbd_accept,
NULL,
NULL);
return; return;
@ -134,117 +162,100 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
void nbd_server_start_options(NbdServerOptions *arg, Error **errp) void nbd_server_start_options(NbdServerOptions *arg, Error **errp)
{ {
nbd_server_start(arg->addr, arg->tls_creds, arg->tls_authz, errp); nbd_server_start(arg->addr, arg->tls_creds, arg->tls_authz,
arg->max_connections, errp);
} }
void qmp_nbd_server_start(SocketAddressLegacy *addr, void qmp_nbd_server_start(SocketAddressLegacy *addr,
bool has_tls_creds, const char *tls_creds, bool has_tls_creds, const char *tls_creds,
bool has_tls_authz, const char *tls_authz, bool has_tls_authz, const char *tls_authz,
bool has_max_connections, uint32_t max_connections,
Error **errp) Error **errp)
{ {
SocketAddress *addr_flat = socket_address_flatten(addr); SocketAddress *addr_flat = socket_address_flatten(addr);
nbd_server_start(addr_flat, tls_creds, tls_authz, errp); nbd_server_start(addr_flat, tls_creds, tls_authz, max_connections, errp);
qapi_free_SocketAddress(addr_flat); qapi_free_SocketAddress(addr_flat);
} }
void qmp_nbd_server_add(BlockExportNbd *arg, Error **errp) void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
{ {
BlockDriverState *bs = NULL; BlockExport *export;
BlockDriverState *bs;
BlockBackend *on_eject_blk; BlockBackend *on_eject_blk;
NBDExport *exp; BlockExportOptions *export_opts;
int64_t len;
AioContext *aio_context;
if (!nbd_server) {
error_setg(errp, "NBD server not running");
return;
}
if (!arg->has_name) {
arg->name = arg->device;
}
if (strlen(arg->name) > NBD_MAX_STRING_SIZE) {
error_setg(errp, "export name '%s' too long", arg->name);
return;
}
if (arg->description && strlen(arg->description) > NBD_MAX_STRING_SIZE) {
error_setg(errp, "description '%s' too long", arg->description);
return;
}
if (nbd_export_find(arg->name)) {
error_setg(errp, "NBD server already has export named '%s'", arg->name);
return;
}
on_eject_blk = blk_by_name(arg->device);
bs = bdrv_lookup_bs(arg->device, arg->device, errp); bs = bdrv_lookup_bs(arg->device, arg->device, errp);
if (!bs) { if (!bs) {
return; return;
} }
aio_context = bdrv_get_aio_context(bs); /*
aio_context_acquire(aio_context); * block-export-add would default to the node-name, but we may have to use
len = bdrv_getlength(bs); * the device name as a default here for compatibility.
if (len < 0) { */
error_setg_errno(errp, -len, if (!arg->has_name) {
"Failed to determine the NBD export's length"); arg->name = arg->device;
goto out;
} }
if (!arg->has_writable) { export_opts = g_new(BlockExportOptions, 1);
arg->writable = false; *export_opts = (BlockExportOptions) {
} .type = BLOCK_EXPORT_TYPE_NBD,
.id = g_strdup(arg->name),
.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),
},
};
/*
* nbd-server-add doesn't complain when a read-only device should be
* exported as writable, but simply downgrades it. This is an error with
* block-export-add.
*/
if (bdrv_is_read_only(bs)) { if (bdrv_is_read_only(bs)) {
arg->writable = false; export_opts->has_writable = true;
export_opts->writable = false;
} }
exp = nbd_export_new(bs, 0, len, arg->name, arg->description, arg->bitmap, export = blk_exp_add(export_opts, errp);
!arg->writable, !arg->writable, if (!export) {
NULL, false, on_eject_blk, errp); goto fail;
if (!exp) {
goto out;
} }
/* The list of named exports has a strong reference to this export now and /*
* our only way of accessing it is through nbd_export_find(), so we can drop * nbd-server-add removes the export when the named BlockBackend used for
* the strong reference that is @exp. */ * @device goes away.
nbd_export_put(exp); */
on_eject_blk = blk_by_name(arg->device);
if (on_eject_blk) {
nbd_export_set_on_eject_blk(export, on_eject_blk);
}
out: fail:
aio_context_release(aio_context); qapi_free_BlockExportOptions(export_opts);
} }
void qmp_nbd_server_remove(const char *name, void qmp_nbd_server_remove(const char *name,
bool has_mode, NbdServerRemoveMode mode, bool has_mode, BlockExportRemoveMode mode,
Error **errp) Error **errp)
{ {
NBDExport *exp; BlockExport *exp;
AioContext *aio_context;
if (!nbd_server) { exp = blk_exp_find(name);
error_setg(errp, "NBD server not running"); if (exp && exp->drv->type != BLOCK_EXPORT_TYPE_NBD) {
error_setg(errp, "Block export '%s' is not an NBD export", name);
return; return;
} }
exp = nbd_export_find(name); qmp_block_export_del(name, has_mode, mode, errp);
if (exp == NULL) {
error_setg(errp, "Export '%s' is not found", name);
return;
}
if (!has_mode) {
mode = NBD_SERVER_REMOVE_MODE_SAFE;
}
aio_context = nbd_export_aio_context(exp);
aio_context_acquire(aio_context);
nbd_export_remove(exp, mode, errp);
aio_context_release(aio_context);
} }
void qmp_nbd_server_stop(Error **errp) void qmp_nbd_server_stop(Error **errp)
@ -254,7 +265,7 @@ void qmp_nbd_server_stop(Error **errp)
return; return;
} }
nbd_export_close_all(); blk_exp_close_all_type(BLOCK_EXPORT_TYPE_NBD);
nbd_server_free(nbd_server); nbd_server_free(nbd_server);
nbd_server = NULL; nbd_server = NULL;

View File

@ -264,6 +264,12 @@ chardev client socket with ``wait`` option (since 4.0)
Character devices creating sockets in client mode should not specify Character devices creating sockets in client mode should not specify
the 'wait' field, which is only applicable to sockets in server mode the 'wait' field, which is only applicable to sockets in server mode
``nbd-server-add`` and ``nbd-server-remove`` (since 5.2)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Use the more generic commands ``block-export-add`` and ``block-export-del``
instead.
Human Monitor Protocol (HMP) commands Human Monitor Protocol (HMP) commands
------------------------------------- -------------------------------------

View File

@ -1,6 +1,6 @@
The QEMU throttling infrastructure The QEMU throttling infrastructure
================================== ==================================
Copyright (C) 2016 Igalia, S.L. Copyright (C) 2016,2020 Igalia, S.L.
Author: Alberto Garcia <berto@igalia.com> Author: Alberto Garcia <berto@igalia.com>
This work is licensed under the terms of the GNU GPL, version 2 or This work is licensed under the terms of the GNU GPL, version 2 or
@ -253,3 +253,109 @@ up. After those 60 seconds the bucket will have leaked 60 x 100 =
Also, due to the way the algorithm works, longer burst can be done at Also, due to the way the algorithm works, longer burst can be done at
a lower I/O rate, e.g. 1000 IOPS during 120 seconds. a lower I/O rate, e.g. 1000 IOPS during 120 seconds.
The 'throttle' block filter
---------------------------
Since QEMU 2.11 it is possible to configure the I/O limits using a
'throttle' block filter. This filter uses the exact same throttling
infrastructure described above but can be used anywhere in the node
graph, allowing for more flexibility.
The user can create an arbitrary number of filters and each one of
them must be assigned to a group that contains the actual I/O limits.
Different filters can use the same group so the limits are shared as
described earlier in "Applying I/O limits to groups of disks".
A group can be created using the object-add QMP function:
{ "execute": "object-add",
"arguments": {
"qom-type": "throttle-group",
"id": "group0",
"props": {
"limits" : {
"iops-total": 1000
"bps-write": 2097152
}
}
}
}
throttle-group has a 'limits' property (of type ThrottleLimits as
defined in qapi/block-core.json) which can be set on creation or later
with 'qom-set'.
A throttle-group can also be created with the -object command line
option but at the moment there is no way to pass a 'limits' parameter
that contains a ThrottleLimits structure. The solution is to set the
individual values directly, like in this example:
-object throttle-group,id=group0,x-iops-total=1000,x-bps-write=2097152
Note however that this is not a stable API (hence the 'x-' prefixes) and
will disappear when -object gains support for structured options and
enables use of 'limits'.
Once we have a throttle-group we can use the throttle block filter,
where the 'file' property must be set to the block device that we want
to filter:
{ "execute": "blockdev-add",
"arguments": {
"options": {
"driver": "qcow2",
"node-name": "disk0",
"file": {
"driver": "file",
"filename": "/path/to/disk.qcow2"
}
}
}
}
{ "execute": "blockdev-add",
"arguments": {
"driver": "throttle",
"node-name": "throttle0",
"throttle-group": "group0",
"file": "disk0"
}
}
A similar setup can also be done with the command line, for example:
-drive driver=throttle,throttle-group=group0,
file.driver=qcow2,file.file.filename=/path/to/disk.qcow2
The scenario described so far is very simple but the throttle block
filter allows for more complex configurations. For example, let's say
that we have three different drives and we want to set I/O limits for
each one of them and an additional set of limits for the combined I/O
of all three drives.
First we would define all throttle groups, one for each one of the
drives and one that would apply to all of them:
-object throttle-group,id=limits0,x-iops-total=2000
-object throttle-group,id=limits1,x-iops-total=2500
-object throttle-group,id=limits2,x-iops-total=3000
-object throttle-group,id=limits012,x-iops-total=4000
Now we can define the drives, and for each one of them we use two
chained throttle filters: the drive's own filter and the combined
filter.
-drive driver=throttle,throttle-group=limits012,
file.driver=throttle,file.throttle-group=limits0
file.file.driver=qcow2,file.file.file.filename=/path/to/disk0.qcow2
-drive driver=throttle,throttle-group=limits012,
file.driver=throttle,file.throttle-group=limits1
file.file.driver=qcow2,file.file.file.filename=/path/to/disk1.qcow2
-drive driver=throttle,throttle-group=limits012,
file.driver=throttle,file.throttle-group=limits2
file.file.driver=qcow2,file.file.file.filename=/path/to/disk2.qcow2
In this example the individual drives have IOPS limits of 2000, 2500
and 3000 respectively but the total combined I/O can never exceed 4000
IOPS.

89
include/block/export.h Normal file
View File

@ -0,0 +1,89 @@
/*
* Declarations for block exports
*
* Copyright (c) 2012, 2020 Red Hat, Inc.
*
* Authors:
* Paolo Bonzini <pbonzini@redhat.com>
* Kevin Wolf <kwolf@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or
* later. See the COPYING file in the top-level directory.
*/
#ifndef BLOCK_EXPORT_H
#define BLOCK_EXPORT_H
#include "qapi/qapi-types-block-export.h"
#include "qemu/queue.h"
typedef struct BlockExport BlockExport;
typedef struct BlockExportDriver {
/* The export type that this driver services */
BlockExportType type;
/*
* The size of the driver-specific state that contains BlockExport as its
* first field.
*/
size_t instance_size;
/* Creates and starts a new block export */
int (*create)(BlockExport *, BlockExportOptions *, Error **);
/*
* Frees a removed block export. This function is only called after all
* references have been dropped.
*/
void (*delete)(BlockExport *);
/*
* Start to disconnect all clients and drop other references held
* internally by the export driver. When the function returns, there may
* still be active references while the export is in the process of
* shutting down.
*/
void (*request_shutdown)(BlockExport *);
} BlockExportDriver;
struct BlockExport {
const BlockExportDriver *drv;
/* Unique identifier for the export */
char *id;
/*
* Reference count for this block export. This includes strong references
* both from the owner (qemu-nbd or the monitor) and clients connected to
* the export.
*/
int refcount;
/*
* True if one of the references in refcount belongs to the user. After the
* user has dropped their reference, they may not e.g. remove the same
* export a second time (which would decrease the refcount without having
* it incremented first).
*/
bool user_owned;
/* The AioContext whose lock protects this BlockExport object. */
AioContext *ctx;
/* The block device to export */
BlockBackend *blk;
/* List entry for block_exports */
QLIST_ENTRY(BlockExport) next;
};
BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp);
BlockExport *blk_exp_find(const char *id);
void blk_exp_ref(BlockExport *exp);
void blk_exp_unref(BlockExport *exp);
void blk_exp_request_shutdown(BlockExport *exp);
void blk_exp_close_all(void);
void blk_exp_close_all_type(BlockExportType type);
#endif

View File

@ -20,11 +20,13 @@
#ifndef NBD_H #ifndef NBD_H
#define NBD_H #define NBD_H
#include "qapi/qapi-types-block.h" #include "block/export.h"
#include "io/channel-socket.h" #include "io/channel-socket.h"
#include "crypto/tlscreds.h" #include "crypto/tlscreds.h"
#include "qapi/error.h" #include "qapi/error.h"
extern const BlockExportDriver blk_exp_nbd;
/* Handshake phase structs - this struct is passed on the wire */ /* Handshake phase structs - this struct is passed on the wire */
struct NBDOption { struct NBDOption {
@ -328,21 +330,10 @@ int nbd_errno_to_system_errno(int err);
typedef struct NBDExport NBDExport; typedef struct NBDExport NBDExport;
typedef struct NBDClient NBDClient; typedef struct NBDClient NBDClient;
NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset, void nbd_export_set_on_eject_blk(BlockExport *exp, BlockBackend *blk);
uint64_t size, const char *name, const char *desc,
const char *bitmap, bool readonly, bool shared,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp);
void nbd_export_close(NBDExport *exp);
void nbd_export_remove(NBDExport *exp, NbdServerRemoveMode mode, Error **errp);
void nbd_export_get(NBDExport *exp);
void nbd_export_put(NBDExport *exp);
BlockBackend *nbd_export_get_blockdev(NBDExport *exp);
AioContext *nbd_export_aio_context(NBDExport *exp); AioContext *nbd_export_aio_context(NBDExport *exp);
NBDExport *nbd_export_find(const char *name); NBDExport *nbd_export_find(const char *name);
void nbd_export_close_all(void);
void nbd_client_new(QIOChannelSocket *sioc, void nbd_client_new(QIOChannelSocket *sioc,
QCryptoTLSCreds *tlscreds, QCryptoTLSCreds *tlscreds,
@ -351,8 +342,11 @@ void nbd_client_new(QIOChannelSocket *sioc,
void nbd_client_get(NBDClient *client); void nbd_client_get(NBDClient *client);
void nbd_client_put(NBDClient *client); void nbd_client_put(NBDClient *client);
void nbd_server_is_qemu_nbd(bool value);
bool nbd_server_is_running(void);
void nbd_server_start(SocketAddress *addr, const char *tls_creds, void nbd_server_start(SocketAddress *addr, const char *tls_creds,
const char *tls_authz, Error **errp); const char *tls_authz, uint32_t max_connections,
Error **errp);
void nbd_server_start_options(NbdServerOptions *arg, Error **errp); void nbd_server_start_options(NbdServerOptions *arg, Error **errp);
/* nbd_read /* nbd_read

View File

@ -970,6 +970,7 @@ subdir('dump')
block_ss.add(files( block_ss.add(files(
'block.c', 'block.c',
'blockdev-nbd.c',
'blockjob.c', 'blockjob.c',
'job.c', 'job.c',
'qemu-io-cmds.c', 'qemu-io-cmds.c',
@ -982,7 +983,6 @@ subdir('block')
blockdev_ss.add(files( blockdev_ss.add(files(
'blockdev.c', 'blockdev.c',
'blockdev-nbd.c',
'iothread.c', 'iothread.c',
'job-qmp.c', 'job-qmp.c',
)) ))

View File

@ -18,6 +18,8 @@
*/ */
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "block/export.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qemu/queue.h" #include "qemu/queue.h"
#include "trace.h" #include "trace.h"
@ -80,20 +82,15 @@ struct NBDRequestData {
}; };
struct NBDExport { struct NBDExport {
int refcount; BlockExport common;
void (*close)(NBDExport *exp);
BlockBackend *blk;
char *name; char *name;
char *description; char *description;
uint64_t dev_offset;
uint64_t size; uint64_t size;
uint16_t nbdflags; uint16_t nbdflags;
QTAILQ_HEAD(, NBDClient) clients; QTAILQ_HEAD(, NBDClient) clients;
QTAILQ_ENTRY(NBDExport) next; QTAILQ_ENTRY(NBDExport) next;
AioContext *ctx;
BlockBackend *eject_notifier_blk; BlockBackend *eject_notifier_blk;
Notifier eject_notifier; Notifier eject_notifier;
@ -102,8 +99,6 @@ struct NBDExport {
}; };
static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports); static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
static QTAILQ_HEAD(, NBDExport) closed_exports =
QTAILQ_HEAD_INITIALIZER(closed_exports);
/* NBDExportMetaContexts represents a list of contexts to be exported, /* NBDExportMetaContexts represents a list of contexts to be exported,
* as selected by NBD_OPT_SET_META_CONTEXT. Also used for * as selected by NBD_OPT_SET_META_CONTEXT. Also used for
@ -498,7 +493,7 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
} }
QTAILQ_INSERT_TAIL(&client->exp->clients, client, next); QTAILQ_INSERT_TAIL(&client->exp->clients, client, next);
nbd_export_get(client->exp); blk_exp_ref(&client->exp->common);
nbd_check_meta_export(client); nbd_check_meta_export(client);
return 0; return 0;
@ -647,7 +642,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
* whether this is OPT_INFO or OPT_GO. */ * whether this is OPT_INFO or OPT_GO. */
/* minimum - 1 for back-compat, or actual if client will obey it. */ /* minimum - 1 for back-compat, or actual if client will obey it. */
if (client->opt == NBD_OPT_INFO || blocksize) { if (client->opt == NBD_OPT_INFO || blocksize) {
check_align = sizes[0] = blk_get_request_alignment(exp->blk); check_align = sizes[0] = blk_get_request_alignment(exp->common.blk);
} else { } else {
sizes[0] = 1; sizes[0] = 1;
} }
@ -656,7 +651,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
* TODO: is blk_bs(blk)->bl.opt_transfer appropriate? */ * TODO: is blk_bs(blk)->bl.opt_transfer appropriate? */
sizes[1] = MAX(4096, sizes[0]); sizes[1] = MAX(4096, sizes[0]);
/* maximum - At most 32M, but smaller as appropriate. */ /* maximum - At most 32M, but smaller as appropriate. */
sizes[2] = MIN(blk_get_max_transfer(exp->blk), NBD_MAX_BUFFER_SIZE); sizes[2] = MIN(blk_get_max_transfer(exp->common.blk), NBD_MAX_BUFFER_SIZE);
trace_nbd_negotiate_handle_info_block_size(sizes[0], sizes[1], sizes[2]); trace_nbd_negotiate_handle_info_block_size(sizes[0], sizes[1], sizes[2]);
sizes[0] = cpu_to_be32(sizes[0]); sizes[0] = cpu_to_be32(sizes[0]);
sizes[1] = cpu_to_be32(sizes[1]); sizes[1] = cpu_to_be32(sizes[1]);
@ -688,7 +683,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
* tolerate all clients, regardless of alignments. * tolerate all clients, regardless of alignments.
*/ */
if (client->opt == NBD_OPT_INFO && !blocksize && if (client->opt == NBD_OPT_INFO && !blocksize &&
blk_get_request_alignment(exp->blk) > 1) { blk_get_request_alignment(exp->common.blk) > 1) {
return nbd_negotiate_send_rep_err(client, return nbd_negotiate_send_rep_err(client,
NBD_REP_ERR_BLOCK_SIZE_REQD, NBD_REP_ERR_BLOCK_SIZE_REQD,
errp, errp,
@ -706,7 +701,7 @@ static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
client->exp = exp; client->exp = exp;
client->check_align = check_align; client->check_align = check_align;
QTAILQ_INSERT_TAIL(&client->exp->clients, client, next); QTAILQ_INSERT_TAIL(&client->exp->clients, client, next);
nbd_export_get(client->exp); blk_exp_ref(&client->exp->common);
nbd_check_meta_export(client); nbd_check_meta_export(client);
rc = 1; rc = 1;
} }
@ -1333,8 +1328,8 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp)
} }
/* Attach the channel to the same AioContext as the export */ /* Attach the channel to the same AioContext as the export */
if (client->exp && client->exp->ctx) { if (client->exp && client->exp->common.ctx) {
qio_channel_attach_aio_context(client->ioc, client->exp->ctx); qio_channel_attach_aio_context(client->ioc, client->exp->common.ctx);
} }
assert(!client->optlen); assert(!client->optlen);
@ -1405,7 +1400,7 @@ void nbd_client_put(NBDClient *client)
g_free(client->tlsauthz); g_free(client->tlsauthz);
if (client->exp) { if (client->exp) {
QTAILQ_REMOVE(&client->exp->clients, client, next); QTAILQ_REMOVE(&client->exp->clients, client, next);
nbd_export_put(client->exp); blk_exp_unref(&client->exp->common);
} }
g_free(client); g_free(client);
} }
@ -1466,7 +1461,7 @@ static void blk_aio_attached(AioContext *ctx, void *opaque)
trace_nbd_blk_aio_attached(exp->name, ctx); trace_nbd_blk_aio_attached(exp->name, ctx);
exp->ctx = ctx; exp->common.ctx = ctx;
QTAILQ_FOREACH(client, &exp->clients, next) { QTAILQ_FOREACH(client, &exp->clients, next) {
qio_channel_attach_aio_context(client->ioc, ctx); qio_channel_attach_aio_context(client->ioc, ctx);
@ -1484,72 +1479,92 @@ static void blk_aio_detach(void *opaque)
NBDExport *exp = opaque; NBDExport *exp = opaque;
NBDClient *client; NBDClient *client;
trace_nbd_blk_aio_detach(exp->name, exp->ctx); trace_nbd_blk_aio_detach(exp->name, exp->common.ctx);
QTAILQ_FOREACH(client, &exp->clients, next) { QTAILQ_FOREACH(client, &exp->clients, next) {
qio_channel_detach_aio_context(client->ioc); qio_channel_detach_aio_context(client->ioc);
} }
exp->ctx = NULL; exp->common.ctx = NULL;
} }
static void nbd_eject_notifier(Notifier *n, void *data) static void nbd_eject_notifier(Notifier *n, void *data)
{ {
NBDExport *exp = container_of(n, NBDExport, eject_notifier); NBDExport *exp = container_of(n, NBDExport, eject_notifier);
AioContext *aio_context;
aio_context = exp->ctx; blk_exp_request_shutdown(&exp->common);
aio_context_acquire(aio_context);
nbd_export_close(exp);
aio_context_release(aio_context);
} }
NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset, void nbd_export_set_on_eject_blk(BlockExport *exp, BlockBackend *blk)
uint64_t size, const char *name, const char *desc,
const char *bitmap, bool readonly, bool shared,
void (*close)(NBDExport *), bool writethrough,
BlockBackend *on_eject_blk, Error **errp)
{ {
AioContext *ctx; NBDExport *nbd_exp = container_of(exp, NBDExport, common);
BlockBackend *blk; assert(exp->drv == &blk_exp_nbd);
NBDExport *exp = g_new0(NBDExport, 1); assert(nbd_exp->eject_notifier_blk == NULL);
uint64_t perm;
blk_ref(blk);
nbd_exp->eject_notifier_blk = blk;
nbd_exp->eject_notifier.notify = nbd_eject_notifier;
blk_add_remove_bs_notifier(blk, &nbd_exp->eject_notifier);
}
static int nbd_export_create(BlockExport *blk_exp, BlockExportOptions *exp_args,
Error **errp)
{
NBDExport *exp = container_of(blk_exp, NBDExport, common);
BlockExportOptionsNbd *arg = &exp_args->u.nbd;
BlockBackend *blk = blk_exp->blk;
int64_t size;
uint64_t perm, shared_perm;
bool readonly = !exp_args->writable;
bool shared = !exp_args->writable;
int ret; int ret;
/* assert(exp_args->type == BLOCK_EXPORT_TYPE_NBD);
* NBD exports are used for non-shared storage migration. Make sure
* that BDRV_O_INACTIVE is cleared and the image is ready for write if (!nbd_server_is_running()) {
* access since the export could be available before migration handover. error_setg(errp, "NBD server not running");
* ctx was acquired in the caller. return -EINVAL;
*/ }
assert(name && strlen(name) <= NBD_MAX_STRING_SIZE);
ctx = bdrv_get_aio_context(bs); if (!arg->has_name) {
bdrv_invalidate_cache(bs, NULL); arg->name = exp_args->node_name;
}
if (strlen(arg->name) > NBD_MAX_STRING_SIZE) {
error_setg(errp, "export name '%s' too long", arg->name);
return -EINVAL;
}
if (arg->description && strlen(arg->description) > NBD_MAX_STRING_SIZE) {
error_setg(errp, "description '%s' too long", arg->description);
return -EINVAL;
}
if (nbd_export_find(arg->name)) {
error_setg(errp, "NBD server already has export named '%s'", arg->name);
return -EEXIST;
}
size = blk_getlength(blk);
if (size < 0) {
error_setg_errno(errp, -size,
"Failed to determine the NBD export's length");
return size;
}
/* Don't allow resize while the NBD server is running, otherwise we don't /* Don't allow resize while the NBD server is running, otherwise we don't
* care what happens with the node. */ * care what happens with the node. */
perm = BLK_PERM_CONSISTENT_READ; blk_get_perm(blk, &perm, &shared_perm);
if (!readonly) { ret = blk_set_perm(blk, perm, shared_perm & ~BLK_PERM_RESIZE, errp);
perm |= BLK_PERM_WRITE;
}
blk = blk_new(ctx, perm,
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED |
BLK_PERM_WRITE | BLK_PERM_GRAPH_MOD);
ret = blk_insert_bs(blk, bs, errp);
if (ret < 0) { if (ret < 0) {
goto fail; return ret;
} }
blk_set_enable_write_cache(blk, !writethrough);
blk_set_allow_aio_context_change(blk, true); blk_set_allow_aio_context_change(blk, true);
exp->refcount = 1;
QTAILQ_INIT(&exp->clients); QTAILQ_INIT(&exp->clients);
exp->blk = blk; exp->name = g_strdup(arg->name);
assert(dev_offset <= INT64_MAX); exp->description = g_strdup(arg->description);
exp->dev_offset = dev_offset;
exp->name = g_strdup(name);
assert(!desc || strlen(desc) <= NBD_MAX_STRING_SIZE);
exp->description = g_strdup(desc);
exp->nbdflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_FLUSH | exp->nbdflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_FLUSH |
NBD_FLAG_SEND_FUA | NBD_FLAG_SEND_CACHE); NBD_FLAG_SEND_FUA | NBD_FLAG_SEND_CACHE);
if (readonly) { if (readonly) {
@ -1561,14 +1576,14 @@ NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
exp->nbdflags |= (NBD_FLAG_SEND_TRIM | NBD_FLAG_SEND_WRITE_ZEROES | exp->nbdflags |= (NBD_FLAG_SEND_TRIM | NBD_FLAG_SEND_WRITE_ZEROES |
NBD_FLAG_SEND_FAST_ZERO); NBD_FLAG_SEND_FAST_ZERO);
} }
assert(size <= INT64_MAX - dev_offset);
exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE); exp->size = QEMU_ALIGN_DOWN(size, BDRV_SECTOR_SIZE);
if (bitmap) { if (arg->bitmap) {
BlockDriverState *bs = blk_bs(blk);
BdrvDirtyBitmap *bm = NULL; BdrvDirtyBitmap *bm = NULL;
while (bs) { while (bs) {
bm = bdrv_find_dirty_bitmap(bs, bitmap); bm = bdrv_find_dirty_bitmap(bs, arg->bitmap);
if (bm != NULL) { if (bm != NULL) {
break; break;
} }
@ -1577,50 +1592,43 @@ NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
} }
if (bm == NULL) { if (bm == NULL) {
error_setg(errp, "Bitmap '%s' is not found", bitmap); ret = -ENOENT;
error_setg(errp, "Bitmap '%s' is not found", arg->bitmap);
goto fail; goto fail;
} }
if (bdrv_dirty_bitmap_check(bm, BDRV_BITMAP_ALLOW_RO, errp)) { if (bdrv_dirty_bitmap_check(bm, BDRV_BITMAP_ALLOW_RO, errp)) {
ret = -EINVAL;
goto fail; goto fail;
} }
if (readonly && bdrv_is_writable(bs) && if (readonly && bdrv_is_writable(bs) &&
bdrv_dirty_bitmap_enabled(bm)) { bdrv_dirty_bitmap_enabled(bm)) {
ret = -EINVAL;
error_setg(errp, error_setg(errp,
"Enabled bitmap '%s' incompatible with readonly export", "Enabled bitmap '%s' incompatible with readonly export",
bitmap); arg->bitmap);
goto fail; goto fail;
} }
bdrv_dirty_bitmap_set_busy(bm, true); bdrv_dirty_bitmap_set_busy(bm, true);
exp->export_bitmap = bm; exp->export_bitmap = bm;
assert(strlen(bitmap) <= BDRV_BITMAP_MAX_NAME_SIZE); assert(strlen(arg->bitmap) <= BDRV_BITMAP_MAX_NAME_SIZE);
exp->export_bitmap_context = g_strdup_printf("qemu:dirty-bitmap:%s", exp->export_bitmap_context = g_strdup_printf("qemu:dirty-bitmap:%s",
bitmap); arg->bitmap);
assert(strlen(exp->export_bitmap_context) < NBD_MAX_STRING_SIZE); assert(strlen(exp->export_bitmap_context) < NBD_MAX_STRING_SIZE);
} }
exp->close = close;
exp->ctx = ctx;
blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp); blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp);
if (on_eject_blk) {
blk_ref(on_eject_blk);
exp->eject_notifier_blk = on_eject_blk;
exp->eject_notifier.notify = nbd_eject_notifier;
blk_add_remove_bs_notifier(on_eject_blk, &exp->eject_notifier);
}
QTAILQ_INSERT_TAIL(&exports, exp, next); QTAILQ_INSERT_TAIL(&exports, exp, next);
nbd_export_get(exp);
return exp; return 0;
fail: fail:
blk_unref(blk);
g_free(exp->name); g_free(exp->name);
g_free(exp->description); g_free(exp->description);
g_free(exp); return ret;
return NULL;
} }
NBDExport *nbd_export_find(const char *name) NBDExport *nbd_export_find(const char *name)
@ -1638,14 +1646,15 @@ NBDExport *nbd_export_find(const char *name)
AioContext * AioContext *
nbd_export_aio_context(NBDExport *exp) nbd_export_aio_context(NBDExport *exp)
{ {
return exp->ctx; return exp->common.ctx;
} }
void nbd_export_close(NBDExport *exp) static void nbd_export_request_shutdown(BlockExport *blk_exp)
{ {
NBDExport *exp = container_of(blk_exp, NBDExport, common);
NBDClient *client, *next; NBDClient *client, *next;
nbd_export_get(exp); blk_exp_ref(&exp->common);
/* /*
* TODO: Should we expand QMP NbdServerRemoveNode enum to allow a * TODO: Should we expand QMP NbdServerRemoveNode enum to allow a
* close mode that stops advertising the export to new clients but * close mode that stops advertising the export to new clients but
@ -1657,100 +1666,45 @@ void nbd_export_close(NBDExport *exp)
client_close(client, true); client_close(client, true);
} }
if (exp->name) { if (exp->name) {
nbd_export_put(exp);
g_free(exp->name); g_free(exp->name);
exp->name = NULL; exp->name = NULL;
QTAILQ_REMOVE(&exports, exp, next); QTAILQ_REMOVE(&exports, exp, next);
QTAILQ_INSERT_TAIL(&closed_exports, exp, next);
} }
blk_exp_unref(&exp->common);
}
static void nbd_export_delete(BlockExport *blk_exp)
{
NBDExport *exp = container_of(blk_exp, NBDExport, common);
assert(exp->name == NULL);
assert(QTAILQ_EMPTY(&exp->clients));
g_free(exp->description); g_free(exp->description);
exp->description = NULL; exp->description = NULL;
nbd_export_put(exp);
}
void nbd_export_remove(NBDExport *exp, NbdServerRemoveMode mode, Error **errp) if (exp->common.blk) {
{ if (exp->eject_notifier_blk) {
ERRP_GUARD(); notifier_remove(&exp->eject_notifier);
if (mode == NBD_SERVER_REMOVE_MODE_HARD || QTAILQ_EMPTY(&exp->clients)) { blk_unref(exp->eject_notifier_blk);
nbd_export_close(exp);
return;
}
assert(mode == NBD_SERVER_REMOVE_MODE_SAFE);
error_setg(errp, "export '%s' still in use", exp->name);
error_append_hint(errp, "Use mode='hard' to force client disconnect\n");
}
void nbd_export_get(NBDExport *exp)
{
assert(exp->refcount > 0);
exp->refcount++;
}
void nbd_export_put(NBDExport *exp)
{
assert(exp->refcount > 0);
if (exp->refcount == 1) {
nbd_export_close(exp);
}
/* nbd_export_close() may theoretically reduce refcount to 0. It may happen
* if someone calls nbd_export_put() on named export not through
* nbd_export_set_name() when refcount is 1. So, let's assert that
* it is > 0.
*/
assert(exp->refcount > 0);
if (--exp->refcount == 0) {
assert(exp->name == NULL);
assert(exp->description == NULL);
if (exp->close) {
exp->close(exp);
} }
blk_remove_aio_context_notifier(exp->common.blk, blk_aio_attached,
blk_aio_detach, exp);
}
if (exp->blk) { if (exp->export_bitmap) {
if (exp->eject_notifier_blk) { bdrv_dirty_bitmap_set_busy(exp->export_bitmap, false);
notifier_remove(&exp->eject_notifier); g_free(exp->export_bitmap_context);
blk_unref(exp->eject_notifier_blk);
}
blk_remove_aio_context_notifier(exp->blk, blk_aio_attached,
blk_aio_detach, exp);
blk_unref(exp->blk);
exp->blk = NULL;
}
if (exp->export_bitmap) {
bdrv_dirty_bitmap_set_busy(exp->export_bitmap, false);
g_free(exp->export_bitmap_context);
}
QTAILQ_REMOVE(&closed_exports, exp, next);
g_free(exp);
aio_wait_kick();
} }
} }
BlockBackend *nbd_export_get_blockdev(NBDExport *exp) const BlockExportDriver blk_exp_nbd = {
{ .type = BLOCK_EXPORT_TYPE_NBD,
return exp->blk; .instance_size = sizeof(NBDExport),
} .create = nbd_export_create,
.delete = nbd_export_delete,
void nbd_export_close_all(void) .request_shutdown = nbd_export_request_shutdown,
{ };
NBDExport *exp, *next;
AioContext *aio_context;
QTAILQ_FOREACH_SAFE(exp, &exports, next, next) {
aio_context = exp->ctx;
aio_context_acquire(aio_context);
nbd_export_close(exp);
aio_context_release(aio_context);
}
AIO_WAIT_WHILE(NULL, !(QTAILQ_EMPTY(&exports) &&
QTAILQ_EMPTY(&closed_exports)));
}
static int coroutine_fn nbd_co_send_iov(NBDClient *client, struct iovec *iov, static int coroutine_fn nbd_co_send_iov(NBDClient *client, struct iovec *iov,
unsigned niov, Error **errp) unsigned niov, Error **errp)
@ -1888,7 +1842,7 @@ static int coroutine_fn nbd_co_send_sparse_read(NBDClient *client,
while (progress < size) { while (progress < size) {
int64_t pnum; int64_t pnum;
int status = bdrv_block_status_above(blk_bs(exp->blk), NULL, int status = bdrv_block_status_above(blk_bs(exp->common.blk), NULL,
offset + progress, offset + progress,
size - progress, &pnum, NULL, size - progress, &pnum, NULL,
NULL); NULL);
@ -1920,7 +1874,7 @@ static int coroutine_fn nbd_co_send_sparse_read(NBDClient *client,
stl_be_p(&chunk.length, pnum); stl_be_p(&chunk.length, pnum);
ret = nbd_co_send_iov(client, iov, 1, errp); ret = nbd_co_send_iov(client, iov, 1, errp);
} else { } else {
ret = blk_pread(exp->blk, offset + progress + exp->dev_offset, ret = blk_pread(exp->common.blk, offset + progress,
data + progress, pnum); data + progress, pnum);
if (ret < 0) { if (ret < 0) {
error_setg_errno(errp, -ret, "reading from file failed"); error_setg_errno(errp, -ret, "reading from file failed");
@ -2185,7 +2139,8 @@ static int nbd_co_receive_request(NBDRequestData *req, NBDRequest *request,
} }
if (request->type != NBD_CMD_CACHE) { if (request->type != NBD_CMD_CACHE) {
req->data = blk_try_blockalign(client->exp->blk, request->len); req->data = blk_try_blockalign(client->exp->common.blk,
request->len);
if (req->data == NULL) { if (req->data == NULL) {
error_setg(errp, "No memory"); error_setg(errp, "No memory");
return -ENOMEM; return -ENOMEM;
@ -2281,7 +2236,7 @@ static coroutine_fn int nbd_do_cmd_read(NBDClient *client, NBDRequest *request,
/* XXX: NBD Protocol only documents use of FUA with WRITE */ /* XXX: NBD Protocol only documents use of FUA with WRITE */
if (request->flags & NBD_CMD_FLAG_FUA) { if (request->flags & NBD_CMD_FLAG_FUA) {
ret = blk_co_flush(exp->blk); ret = blk_co_flush(exp->common.blk);
if (ret < 0) { if (ret < 0) {
return nbd_send_generic_reply(client, request->handle, ret, return nbd_send_generic_reply(client, request->handle, ret,
"flush failed", errp); "flush failed", errp);
@ -2295,8 +2250,7 @@ static coroutine_fn int nbd_do_cmd_read(NBDClient *client, NBDRequest *request,
data, request->len, errp); data, request->len, errp);
} }
ret = blk_pread(exp->blk, request->from + exp->dev_offset, data, ret = blk_pread(exp->common.blk, request->from, data, request->len);
request->len);
if (ret < 0) { if (ret < 0) {
return nbd_send_generic_reply(client, request->handle, ret, return nbd_send_generic_reply(client, request->handle, ret,
"reading from file failed", errp); "reading from file failed", errp);
@ -2331,7 +2285,7 @@ static coroutine_fn int nbd_do_cmd_cache(NBDClient *client, NBDRequest *request,
assert(request->type == NBD_CMD_CACHE); assert(request->type == NBD_CMD_CACHE);
ret = blk_co_preadv(exp->blk, request->from + exp->dev_offset, request->len, ret = blk_co_preadv(exp->common.blk, request->from, request->len,
NULL, BDRV_REQ_COPY_ON_READ | BDRV_REQ_PREFETCH); NULL, BDRV_REQ_COPY_ON_READ | BDRV_REQ_PREFETCH);
return nbd_send_generic_reply(client, request->handle, ret, return nbd_send_generic_reply(client, request->handle, ret,
@ -2362,8 +2316,8 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
if (request->flags & NBD_CMD_FLAG_FUA) { if (request->flags & NBD_CMD_FLAG_FUA) {
flags |= BDRV_REQ_FUA; flags |= BDRV_REQ_FUA;
} }
ret = blk_pwrite(exp->blk, request->from + exp->dev_offset, ret = blk_pwrite(exp->common.blk, request->from, data, request->len,
data, request->len, flags); flags);
return nbd_send_generic_reply(client, request->handle, ret, return nbd_send_generic_reply(client, request->handle, ret,
"writing to file failed", errp); "writing to file failed", errp);
@ -2384,8 +2338,7 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
int align = client->check_align ?: 1; int align = client->check_align ?: 1;
int len = MIN(request->len, QEMU_ALIGN_DOWN(BDRV_REQUEST_MAX_BYTES, int len = MIN(request->len, QEMU_ALIGN_DOWN(BDRV_REQUEST_MAX_BYTES,
align)); align));
ret = blk_pwrite_zeroes(exp->blk, request->from + exp->dev_offset, ret = blk_pwrite_zeroes(exp->common.blk, request->from, len, flags);
len, flags);
request->len -= len; request->len -= len;
request->from += len; request->from += len;
} }
@ -2397,7 +2350,7 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
abort(); abort();
case NBD_CMD_FLUSH: case NBD_CMD_FLUSH:
ret = blk_co_flush(exp->blk); ret = blk_co_flush(exp->common.blk);
return nbd_send_generic_reply(client, request->handle, ret, return nbd_send_generic_reply(client, request->handle, ret,
"flush failed", errp); "flush failed", errp);
@ -2408,13 +2361,12 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
int align = client->check_align ?: 1; int align = client->check_align ?: 1;
int len = MIN(request->len, QEMU_ALIGN_DOWN(BDRV_REQUEST_MAX_BYTES, int len = MIN(request->len, QEMU_ALIGN_DOWN(BDRV_REQUEST_MAX_BYTES,
align)); align));
ret = blk_co_pdiscard(exp->blk, request->from + exp->dev_offset, ret = blk_co_pdiscard(exp->common.blk, request->from, len);
len);
request->len -= len; request->len -= len;
request->from += len; request->from += len;
} }
if (ret >= 0 && request->flags & NBD_CMD_FLAG_FUA) { if (ret >= 0 && request->flags & NBD_CMD_FLAG_FUA) {
ret = blk_co_flush(exp->blk); ret = blk_co_flush(exp->common.blk);
} }
return nbd_send_generic_reply(client, request->handle, ret, return nbd_send_generic_reply(client, request->handle, ret,
"discard failed", errp); "discard failed", errp);
@ -2432,7 +2384,8 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
if (client->export_meta.base_allocation) { if (client->export_meta.base_allocation) {
ret = nbd_co_send_block_status(client, request->handle, ret = nbd_co_send_block_status(client, request->handle,
blk_bs(exp->blk), request->from, blk_bs(exp->common.blk),
request->from,
request->len, dont_fragment, request->len, dont_fragment,
!client->export_meta.bitmap, !client->export_meta.bitmap,
NBD_META_ID_BASE_ALLOCATION, NBD_META_ID_BASE_ALLOCATION,
@ -2546,7 +2499,7 @@ static void nbd_client_receive_next_request(NBDClient *client)
if (!client->recv_coroutine && client->nb_requests < MAX_NBD_REQUESTS) { if (!client->recv_coroutine && client->nb_requests < MAX_NBD_REQUESTS) {
nbd_client_get(client); nbd_client_get(client);
client->recv_coroutine = qemu_coroutine_create(nbd_trip, client); client->recv_coroutine = qemu_coroutine_create(nbd_trip, client);
aio_co_schedule(client->exp->ctx, client->recv_coroutine); aio_co_schedule(client->exp->common.ctx, client->recv_coroutine);
} }
} }

View File

@ -5194,172 +5194,6 @@
'iothread': 'StrOrNull', 'iothread': 'StrOrNull',
'*force': 'bool' } } '*force': 'bool' } }
##
# @NbdServerOptions:
#
# @addr: Address on which to listen.
# @tls-creds: ID of the TLS credentials object (since 2.6).
# @tls-authz: ID of the QAuthZ authorization object used to validate
# the client's x509 distinguished name. This object is
# is only resolved at time of use, so can be deleted and
# recreated on the fly while the NBD server is active.
# If missing, it will default to denying access (since 4.0).
#
# Keep this type consistent with the nbd-server-start arguments. The only
# intended difference is using SocketAddress instead of SocketAddressLegacy.
#
# Since: 4.2
##
{ 'struct': 'NbdServerOptions',
'data': { 'addr': 'SocketAddress',
'*tls-creds': 'str',
'*tls-authz': 'str'} }
##
# @nbd-server-start:
#
# Start an NBD server listening on the given host and port. Block
# devices can then be exported using @nbd-server-add. The NBD
# server will present them as named exports; for example, another
# QEMU instance could refer to them as "nbd:HOST:PORT:exportname=NAME".
#
# Keep this type consistent with the NbdServerOptions type. The only intended
# difference is using SocketAddressLegacy instead of SocketAddress.
#
# @addr: Address on which to listen.
# @tls-creds: ID of the TLS credentials object (since 2.6).
# @tls-authz: ID of the QAuthZ authorization object used to validate
# the client's x509 distinguished name. This object is
# is only resolved at time of use, so can be deleted and
# recreated on the fly while the NBD server is active.
# If missing, it will default to denying access (since 4.0).
#
# Returns: error if the server is already running.
#
# Since: 1.3.0
##
{ 'command': 'nbd-server-start',
'data': { 'addr': 'SocketAddressLegacy',
'*tls-creds': 'str',
'*tls-authz': 'str'} }
##
# @BlockExportNbd:
#
# An NBD block export.
#
# @device: The device name or node name of the node to be exported
#
# @name: Export name. If unspecified, the @device parameter is used as the
# export name. (Since 2.12)
#
# @description: Free-form description of the export, up to 4096 bytes.
# (Since 5.0)
#
# @writable: Whether clients should be able to write to the device via the
# NBD connection (default false).
#
# @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': 'BlockExportNbd',
'data': {'device': 'str', '*name': 'str', '*description': 'str',
'*writable': 'bool', '*bitmap': 'str' } }
##
# @nbd-server-add:
#
# Export a block node to QEMU's embedded NBD server.
#
# Returns: error if the server is not running, or export with the same name
# already exists.
#
# Since: 1.3.0
##
{ 'command': 'nbd-server-add',
'data': 'BlockExportNbd', 'boxed': true }
##
# @NbdServerRemoveMode:
#
# Mode for removing an NBD export.
#
# @safe: Remove export if there are no existing connections, fail otherwise.
#
# @hard: Drop all connections immediately and remove export.
#
# Potential additional modes to be added in the future:
#
# hide: Just hide export from new clients, leave existing connections as is.
# Remove export after all clients are disconnected.
#
# soft: Hide export from new clients, answer with ESHUTDOWN for all further
# requests from existing clients.
#
# Since: 2.12
##
{'enum': 'NbdServerRemoveMode', 'data': ['safe', 'hard']}
##
# @nbd-server-remove:
#
# Remove NBD export by name.
#
# @name: Export name.
#
# @mode: Mode of command operation. See @NbdServerRemoveMode description.
# Default is 'safe'.
#
# Returns: error if
# - the server is not running
# - export is not found
# - mode is 'safe' and there are existing connections
#
# Since: 2.12
##
{ 'command': 'nbd-server-remove',
'data': {'name': 'str', '*mode': 'NbdServerRemoveMode'} }
##
# @nbd-server-stop:
#
# Stop QEMU's embedded NBD server, and unregister all devices previously
# added via @nbd-server-add.
#
# Since: 1.3.0
##
{ 'command': 'nbd-server-stop' }
##
# @BlockExportType:
#
# An enumeration of block export types
#
# @nbd: NBD export
#
# Since: 4.2
##
{ 'enum': 'BlockExportType',
'data': [ 'nbd' ] }
##
# @BlockExport:
#
# Describes a block export, i.e. how single node should be exported on an
# external interface.
#
# Since: 4.2
##
{ 'union': 'BlockExport',
'base': { 'type': 'BlockExportType' },
'discriminator': 'type',
'data': {
'nbd': 'BlockExportNbd'
} }
## ##
# @QuorumOpType: # @QuorumOpType:
# #

291
qapi/block-export.json Normal file
View File

@ -0,0 +1,291 @@
# -*- Mode: Python -*-
# vim: filetype=python
##
# == Block device exports
##
{ 'include': 'sockets.json' }
##
# @NbdServerOptions:
#
# Keep this type consistent with the nbd-server-start arguments. The only
# intended difference is using SocketAddress instead of SocketAddressLegacy.
#
# @addr: Address on which to listen.
# @tls-creds: ID of the TLS credentials object (since 2.6).
# @tls-authz: ID of the QAuthZ authorization object used to validate
# the client's x509 distinguished name. This object is
# is only resolved at time of use, so can be deleted and
# recreated on the fly while the NBD server is active.
# If missing, it will default to denying access (since 4.0).
# @max-connections: The maximum number of connections to allow at the same
# time, 0 for unlimited. (since 5.2; default: 0)
#
# Since: 4.2
##
{ 'struct': 'NbdServerOptions',
'data': { 'addr': 'SocketAddress',
'*tls-creds': 'str',
'*tls-authz': 'str',
'*max-connections': 'uint32' } }
##
# @nbd-server-start:
#
# Start an NBD server listening on the given host and port. Block
# devices can then be exported using @nbd-server-add. The NBD
# server will present them as named exports; for example, another
# QEMU instance could refer to them as "nbd:HOST:PORT:exportname=NAME".
#
# Keep this type consistent with the NbdServerOptions type. The only intended
# difference is using SocketAddressLegacy instead of SocketAddress.
#
# @addr: Address on which to listen.
# @tls-creds: ID of the TLS credentials object (since 2.6).
# @tls-authz: ID of the QAuthZ authorization object used to validate
# the client's x509 distinguished name. This object is
# is only resolved at time of use, so can be deleted and
# recreated on the fly while the NBD server is active.
# If missing, it will default to denying access (since 4.0).
# @max-connections: The maximum number of connections to allow at the same
# time, 0 for unlimited. (since 5.2; default: 0)
#
# Returns: error if the server is already running.
#
# Since: 1.3.0
##
{ 'command': 'nbd-server-start',
'data': { 'addr': 'SocketAddressLegacy',
'*tls-creds': 'str',
'*tls-authz': 'str',
'*max-connections': 'uint32' } }
##
# @BlockExportOptionsNbd:
#
# An NBD block export (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)
#
# @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': 'BlockExportOptionsNbd',
'data': { '*name': 'str', '*description': 'str',
'*bitmap': 'str' } }
##
# @NbdServerAddOptions:
#
# An NBD block export.
#
# @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).
#
# Since: 5.0
##
{ 'struct': 'NbdServerAddOptions',
'base': 'BlockExportOptionsNbd',
'data': { 'device': 'str',
'*writable': 'bool' } }
##
# @nbd-server-add:
#
# Export a block node to QEMU's embedded NBD server.
#
# The export name will be used as the id for the resulting block export.
#
# Features:
# @deprecated: This command is deprecated. Use @block-export-add instead.
#
# Returns: error if the server is not running, or export with the same name
# already exists.
#
# Since: 1.3.0
##
{ 'command': 'nbd-server-add',
'data': 'NbdServerAddOptions', 'boxed': true, 'features': ['deprecated'] }
##
# @BlockExportRemoveMode:
#
# Mode for removing a block export.
#
# @safe: Remove export if there are no existing connections, fail otherwise.
#
# @hard: Drop all connections immediately and remove export.
#
# Potential additional modes to be added in the future:
#
# hide: Just hide export from new clients, leave existing connections as is.
# Remove export after all clients are disconnected.
#
# soft: Hide export from new clients, answer with ESHUTDOWN for all further
# requests from existing clients.
#
# Since: 2.12
##
{'enum': 'BlockExportRemoveMode', 'data': ['safe', 'hard']}
##
# @nbd-server-remove:
#
# Remove NBD export by name.
#
# @name: Block export id.
#
# @mode: Mode of command operation. See @BlockExportRemoveMode description.
# Default is 'safe'.
#
# Features:
# @deprecated: This command is deprecated. Use @block-export-del instead.
#
# Returns: error if
# - the server is not running
# - export is not found
# - mode is 'safe' and there are existing connections
#
# Since: 2.12
##
{ 'command': 'nbd-server-remove',
'data': {'name': 'str', '*mode': 'BlockExportRemoveMode'},
'features': ['deprecated'] }
##
# @nbd-server-stop:
#
# Stop QEMU's embedded NBD server, and unregister all devices previously
# added via @nbd-server-add.
#
# Since: 1.3.0
##
{ 'command': 'nbd-server-stop' }
##
# @BlockExportType:
#
# An enumeration of block export types
#
# @nbd: NBD export
#
# Since: 4.2
##
{ 'enum': 'BlockExportType',
'data': [ 'nbd' ] }
##
# @BlockExportOptions:
#
# Describes a block export, i.e. how single node should be exported on an
# external interface.
#
# @id: A unique identifier for the block export (across all export types)
#
# @node-name: The node name of the block node to be exported (since: 5.2)
#
# @writable: True if clients should be able to write to the export
# (default false)
#
# @writethrough: If true, caches are flushed after every write request to the
# export before completion is signalled. (since: 5.2;
# default: false)
#
# Since: 4.2
##
{ 'union': 'BlockExportOptions',
'base': { 'type': 'BlockExportType',
'id': 'str',
'node-name': 'str',
'*writable': 'bool',
'*writethrough': 'bool' },
'discriminator': 'type',
'data': {
'nbd': 'BlockExportOptionsNbd'
} }
##
# @block-export-add:
#
# Creates a new block export.
#
# Since: 5.2
##
{ 'command': 'block-export-add',
'data': 'BlockExportOptions', 'boxed': true }
##
# @block-export-del:
#
# Request to remove a block export. This drops the user's reference to the
# export, but the export may still stay around after this command returns until
# the shutdown of the export has completed.
#
# @id: Block export id.
#
# @mode: Mode of command operation. See @BlockExportRemoveMode description.
# Default is 'safe'.
#
# Returns: Error if the export is not found or @mode is 'safe' and the export
# is still in use (e.g. by existing client connections)
#
# Since: 5.2
##
{ 'command': 'block-export-del',
'data': { 'id': 'str', '*mode': 'BlockExportRemoveMode' } }
##
# @BLOCK_EXPORT_DELETED:
#
# Emitted when a block export is removed and its id can be reused.
#
# @id: Block export id.
#
# Since: 5.2
##
{ 'event': 'BLOCK_EXPORT_DELETED',
'data': { 'id': 'str' } }
##
# @BlockExportInfo:
#
# Information about a single block export.
#
# @id: The unique identifier for the block export
#
# @type: The block export type
#
# @node-name: The node name of the block node that is exported
#
# @shutting-down: True if the export is shutting down (e.g. after a
# block-export-del command, but before the shutdown has
# completed)
#
# Since: 5.2
##
{ 'struct': 'BlockExportInfo',
'data': { 'id': 'str',
'type': 'BlockExportType',
'node-name': 'str',
'shutting-down': 'bool' } }
##
# @query-block-exports:
#
# Returns: A list of BlockExportInfo describing all block exports
#
# Since: 5.2
##
{ 'command': 'query-block-exports', 'returns': ['BlockExportInfo'] }

View File

@ -17,8 +17,9 @@ qapi_all_modules = [
'acpi', 'acpi',
'audio', 'audio',
'authz', 'authz',
'block-core',
'block', 'block',
'block-core',
'block-export',
'char', 'char',
'common', 'common',
'control', 'control',
@ -49,6 +50,7 @@ qapi_all_modules = [
qapi_storage_daemon_modules = [ qapi_storage_daemon_modules = [
'block-core', 'block-core',
'block-export',
'char', 'char',
'common', 'common',
'control', 'control',

View File

@ -66,6 +66,7 @@
{ 'include': 'run-state.json' } { 'include': 'run-state.json' }
{ 'include': 'crypto.json' } { 'include': 'crypto.json' }
{ 'include': 'block.json' } { 'include': 'block.json' }
{ 'include': 'block-export.json' }
{ 'include': 'char.json' } { 'include': 'char.json' }
{ 'include': 'dump.json' } { 'include': 'dump.json' }
{ 'include': 'job.json' } { 'include': 'job.json' }

View File

@ -2383,14 +2383,7 @@ static const cmdinfo_t sleep_cmd = {
static void help_oneline(const char *cmd, const cmdinfo_t *ct) static void help_oneline(const char *cmd, const cmdinfo_t *ct)
{ {
if (cmd) { printf("%s ", cmd);
printf("%s ", cmd);
} else {
printf("%s ", ct->name);
if (ct->altname) {
printf("(or %s) ", ct->altname);
}
}
if (ct->args) { if (ct->args) {
printf("%s ", ct->args); printf("%s ", ct->args);
@ -2420,7 +2413,7 @@ static int help_f(BlockBackend *blk, int argc, char **argv)
{ {
const cmdinfo_t *ct; const cmdinfo_t *ct;
if (argc == 1) { if (argc < 2) {
help_all(); help_all();
return 0; return 0;
} }

View File

@ -65,12 +65,11 @@
#define MBR_SIZE 512 #define MBR_SIZE 512
static NBDExport *export;
static int verbose; static int verbose;
static char *srcpath; static char *srcpath;
static SocketAddress *saddr; static SocketAddress *saddr;
static int persistent = 0; static int persistent = 0;
static enum { RUNNING, TERMINATE, TERMINATING, TERMINATED } state; static enum { RUNNING, TERMINATE, TERMINATED } state;
static int shared = 1; static int shared = 1;
static int nb_fds; static int nb_fds;
static QIONetListener *server; static QIONetListener *server;
@ -332,12 +331,6 @@ static int nbd_can_accept(void)
return state == RUNNING && nb_fds < shared; return state == RUNNING && nb_fds < shared;
} }
static void nbd_export_closed(NBDExport *export)
{
assert(state == TERMINATING);
state = TERMINATED;
}
static void nbd_update_server_watch(void); static void nbd_update_server_watch(void);
static void nbd_client_closed(NBDClient *client, bool negotiated) static void nbd_client_closed(NBDClient *client, bool negotiated)
@ -524,7 +517,6 @@ int main(int argc, char **argv)
const char *port = NULL; const char *port = NULL;
char *sockpath = NULL; char *sockpath = NULL;
char *device = NULL; char *device = NULL;
int64_t fd_size;
QemuOpts *sn_opts = NULL; QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = 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:B:L";
@ -587,6 +579,7 @@ int main(int argc, char **argv)
int old_stderr = -1; int old_stderr = -1;
unsigned socket_activation; unsigned socket_activation;
const char *pid_file_name = NULL; const char *pid_file_name = NULL;
BlockExportOptions *export_opts;
#if HAVE_NBD_DEVICE #if HAVE_NBD_DEVICE
/* The client thread uses SIGTERM to interrupt the server. A signal /* The client thread uses SIGTERM to interrupt the server. A signal
@ -1037,6 +1030,17 @@ int main(int argc, char **argv)
} }
bs = blk_bs(blk); bs = blk_bs(blk);
if (dev_offset) {
QDict *raw_opts = qdict_new();
qdict_put_str(raw_opts, "driver", "raw");
qdict_put_str(raw_opts, "file", bs->node_name);
qdict_put_int(raw_opts, "offset", dev_offset);
bs = bdrv_open(NULL, NULL, raw_opts, flags, &error_fatal);
blk_remove_bs(blk);
blk_insert_bs(blk, bs, &error_fatal);
bdrv_unref(bs);
}
blk_set_enable_write_cache(blk, !writethrough); blk_set_enable_write_cache(blk, !writethrough);
if (sn_opts) { if (sn_opts) {
@ -1054,24 +1058,29 @@ int main(int argc, char **argv)
} }
bs->detect_zeroes = detect_zeroes; bs->detect_zeroes = detect_zeroes;
fd_size = blk_getlength(blk);
if (fd_size < 0) {
error_report("Failed to determine the image length: %s",
strerror(-fd_size));
exit(EXIT_FAILURE);
}
if (dev_offset >= fd_size) { nbd_server_is_qemu_nbd(true);
error_report("Offset (%" PRIu64 ") has to be smaller than the image "
"size (%" PRId64 ")", dev_offset, fd_size);
exit(EXIT_FAILURE);
}
fd_size -= dev_offset;
export = nbd_export_new(bs, dev_offset, fd_size, export_name, export_opts = g_new(BlockExportOptions, 1);
export_description, bitmap, readonly, shared > 1, *export_opts = (BlockExportOptions) {
nbd_export_closed, writethrough, NULL, .type = BLOCK_EXPORT_TYPE_NBD,
&error_fatal); .id = g_strdup("qemu-nbd-export"),
.node_name = g_strdup(bdrv_get_node_name(bs)),
.has_writethrough = true,
.writethrough = writethrough,
.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),
},
};
blk_exp_add(export_opts, &error_fatal);
qapi_free_BlockExportOptions(export_opts);
if (device) { if (device) {
#if HAVE_NBD_DEVICE #if HAVE_NBD_DEVICE
@ -1111,10 +1120,8 @@ int main(int argc, char **argv)
do { do {
main_loop_wait(false); main_loop_wait(false);
if (state == TERMINATE) { if (state == TERMINATE) {
state = TERMINATING; blk_exp_close_all();
nbd_export_close(export); state = TERMINATED;
nbd_export_put(export);
export = NULL;
} }
} while (state != TERMINATED); } while (state != TERMINATED);

View File

@ -16,6 +16,7 @@
{ 'include': '../../qapi/pragma.json' } { 'include': '../../qapi/pragma.json' }
{ 'include': '../../qapi/block-core.json' } { 'include': '../../qapi/block-core.json' }
{ 'include': '../../qapi/block-export.json' }
{ 'include': '../../qapi/char.json' } { 'include': '../../qapi/char.json' }
{ 'include': '../../qapi/common.json' } { 'include': '../../qapi/common.json' }
{ 'include': '../../qapi/control.json' } { 'include': '../../qapi/control.json' }

View File

@ -35,8 +35,8 @@
#include "monitor/monitor-internal.h" #include "monitor/monitor-internal.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "qapi/qapi-visit-block.h"
#include "qapi/qapi-visit-block-core.h" #include "qapi/qapi-visit-block-core.h"
#include "qapi/qapi-visit-block-export.h"
#include "qapi/qapi-visit-control.h" #include "qapi/qapi-visit-control.h"
#include "qapi/qmp/qdict.h" #include "qapi/qmp/qdict.h"
#include "qapi/qmp/qstring.h" #include "qapi/qmp/qstring.h"
@ -92,7 +92,7 @@ static void help(void)
" --chardev <options> configure a character device backend\n" " --chardev <options> configure a character device backend\n"
" (see the qemu(1) man page for possible options)\n" " (see the qemu(1) man page for possible options)\n"
"\n" "\n"
" --export [type=]nbd,device=<node-name>[,name=<export-name>]\n" " --export [type=]nbd,id=<id>,node-name=<node-name>[,name=<export-name>]\n"
" [,writable=on|off][,bitmap=<name>]\n" " [,writable=on|off][,bitmap=<name>]\n"
" export the specified block node over NBD\n" " export the specified block node over NBD\n"
" (requires --nbd-server)\n" " (requires --nbd-server)\n"
@ -101,9 +101,9 @@ static void help(void)
" configure a QMP monitor\n" " configure a QMP monitor\n"
"\n" "\n"
" --nbd-server addr.type=inet,addr.host=<host>,addr.port=<port>\n" " --nbd-server addr.type=inet,addr.host=<host>,addr.port=<port>\n"
" [,tls-creds=<id>][,tls-authz=<id>]\n" " [,tls-creds=<id>][,tls-authz=<id>][,max-connections=<n>]\n"
" --nbd-server addr.type=unix,addr.path=<path>\n" " --nbd-server addr.type=unix,addr.path=<path>\n"
" [,tls-creds=<id>][,tls-authz=<id>]\n" " [,tls-creds=<id>][,tls-authz=<id>][,max-connections=<n>]\n"
" start an NBD server for exporting block nodes\n" " start an NBD server for exporting block nodes\n"
"\n" "\n"
" --object help list object types that can be added\n" " --object help list object types that can be added\n"
@ -150,17 +150,6 @@ static void init_qmp_commands(void)
qmp_marshal_qmp_capabilities, QCO_ALLOW_PRECONFIG); qmp_marshal_qmp_capabilities, QCO_ALLOW_PRECONFIG);
} }
static void init_export(BlockExport *export, Error **errp)
{
switch (export->type) {
case BLOCK_EXPORT_TYPE_NBD:
qmp_nbd_server_add(&export->u.nbd, errp);
break;
default:
g_assert_not_reached();
}
}
static void process_options(int argc, char *argv[]) static void process_options(int argc, char *argv[])
{ {
int c; int c;
@ -235,14 +224,14 @@ static void process_options(int argc, char *argv[])
case OPTION_EXPORT: case OPTION_EXPORT:
{ {
Visitor *v; Visitor *v;
BlockExport *export; BlockExportOptions *export;
v = qobject_input_visitor_new_str(optarg, "type", &error_fatal); v = qobject_input_visitor_new_str(optarg, "type", &error_fatal);
visit_type_BlockExport(v, NULL, &export, &error_fatal); visit_type_BlockExportOptions(v, NULL, &export, &error_fatal);
visit_free(v); visit_free(v);
init_export(export, &error_fatal); qmp_block_export_add(export, &error_fatal);
qapi_free_BlockExport(export); qapi_free_BlockExportOptions(export);
break; break;
} }
case OPTION_MONITOR: case OPTION_MONITOR:

View File

@ -46,6 +46,11 @@ if ! command -v bash >/dev/null 2>&1 ; then
exit 0 exit 0
fi fi
if LANG=C bash --version | grep -q 'GNU bash, version [123]' ; then
echo "bash version too old ==> Not running the qemu-iotests."
exit 0
fi
if ! (sed --version | grep 'GNU sed') > /dev/null 2>&1 ; then if ! (sed --version | grep 'GNU sed') > /dev/null 2>&1 ; then
if ! command -v gsed >/dev/null 2>&1; then if ! command -v gsed >/dev/null 2>&1; then
echo "GNU sed not available ==> Not running the qemu-iotests." echo "GNU sed not available ==> Not running the qemu-iotests."

View File

@ -81,10 +81,17 @@ $QEMU_IO_PROG -f raw -r -c 'read -P 42 0 64k' \
"nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \ "nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \
| _filter_qemu_io | _filter_nbd | _filter_qemu_io | _filter_nbd
# The order of 'return' and the BLOCK_EXPORT_DELETED event is undefined. Just
# wait until we've twice seen one of them. Filter the 'return' line out so that
# the output is defined.
_send_qemu_cmd $QEMU_HANDLE \ _send_qemu_cmd $QEMU_HANDLE \
"{ 'execute': 'eject', "{ 'execute': 'eject',
'arguments': { 'device': 'drv' }}" \ 'arguments': { 'device': 'drv' }}" \
'return' 'return\|BLOCK_EXPORT_DELETED' |
grep -v 'return'
_send_qemu_cmd $QEMU_HANDLE '' 'return\|BLOCK_EXPORT_DELETED' |
grep -v 'return'
$QEMU_IO_PROG -f raw -r -c close \ $QEMU_IO_PROG -f raw -r -c close \
"nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \ "nbd+unix:///drv?socket=$SOCK_DIR/nbd" 2>&1 \

View File

@ -11,7 +11,7 @@ wrote 65536/65536 bytes at offset 0
read 65536/65536 bytes at offset 0 read 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{ 'execute': 'eject', 'arguments': { 'device': 'drv' }} { 'execute': 'eject', 'arguments': { 'device': 'drv' }}
{"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "drv"}}
qemu-io: can't open device nbd+unix:///drv?socket=SOCK_DIR/nbd: Requested export not available qemu-io: can't open device nbd+unix:///drv?socket=SOCK_DIR/nbd: Requested export not available
server reported: export 'drv' not present server reported: export 'drv' not present
{ 'execute': 'quit' } { 'execute': 'quit' }

View File

@ -45,7 +45,7 @@ exports available: 0
{"execute":"nbd-server-add", "arguments":{"device":"nosuch"}} {"execute":"nbd-server-add", "arguments":{"device":"nosuch"}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}} {"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"execute":"nbd-server-add", "arguments":{"device":"n"}} {"execute":"nbd-server-add", "arguments":{"device":"n"}}
{"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}} {"error": {"class": "GenericError", "desc": "Block export id 'n' is already in use"}}
{"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b2"}} {"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b2"}}
{"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}} {"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}}
{"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b3"}} {"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b3"}}
@ -102,8 +102,10 @@ read 2097152/2097152 bytes at offset 2097152
{"execute":"nbd-server-remove", "arguments":{"name":"n"}} {"execute":"nbd-server-remove", "arguments":{"name":"n"}}
{"return": {}} {"return": {}}
{"execute":"nbd-server-remove", "arguments":{"name":"n2"}} {"execute":"nbd-server-remove", "arguments":{"name":"n2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "n"}}
{"return": {}} {"return": {}}
{"execute":"nbd-server-remove", "arguments":{"name":"n2"}} {"execute":"nbd-server-remove", "arguments":{"name":"n2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "n2"}}
{"error": {"class": "GenericError", "desc": "Export 'n2' is not found"}} {"error": {"class": "GenericError", "desc": "Export 'n2' is not found"}}
{"execute":"nbd-server-stop"} {"execute":"nbd-server-stop"}
{"return": {}} {"return": {}}
@ -126,7 +128,7 @@ exports available: 0
{"execute":"nbd-server-add", "arguments":{"device":"nosuch"}} {"execute":"nbd-server-add", "arguments":{"device":"nosuch"}}
{"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}} {"error": {"class": "GenericError", "desc": "Cannot find device=nosuch nor node_name=nosuch"}}
{"execute":"nbd-server-add", "arguments":{"device":"n"}} {"execute":"nbd-server-add", "arguments":{"device":"n"}}
{"error": {"class": "GenericError", "desc": "NBD server already has export named 'n'"}} {"error": {"class": "GenericError", "desc": "Block export id 'n' is already in use"}}
{"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b2"}} {"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b2"}}
{"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}} {"error": {"class": "GenericError", "desc": "Enabled bitmap 'b2' incompatible with readonly export"}}
{"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b3"}} {"execute":"nbd-server-add", "arguments":{"device":"n", "name":"n2", "bitmap":"b3"}}
@ -183,8 +185,10 @@ read 2097152/2097152 bytes at offset 2097152
{"execute":"nbd-server-remove", "arguments":{"name":"n"}} {"execute":"nbd-server-remove", "arguments":{"name":"n"}}
{"return": {}} {"return": {}}
{"execute":"nbd-server-remove", "arguments":{"name":"n2"}} {"execute":"nbd-server-remove", "arguments":{"name":"n2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "n"}}
{"return": {}} {"return": {}}
{"execute":"nbd-server-remove", "arguments":{"name":"n2"}} {"execute":"nbd-server-remove", "arguments":{"name":"n2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "n2"}}
{"error": {"class": "GenericError", "desc": "Export 'n2' is not found"}} {"error": {"class": "GenericError", "desc": "Export 'n2' is not found"}}
{"execute":"nbd-server-stop"} {"execute":"nbd-server-stop"}
{"return": {}} {"return": {}}

132
tests/qemu-iotests/307 Executable file
View File

@ -0,0 +1,132 @@
#!/usr/bin/env python3
#
# Copyright (C) 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/>.
#
# Creator/Owner: Kevin Wolf <kwolf@redhat.com>
#
# Test the block export QAPI interfaces
import iotests
import os
# Need a writable image format (but not vpc, which rounds the image size, nor
# luks which requires special command lines)
iotests.script_initialize(
supported_fmts=['generic'],
unsupported_fmts=['luks', 'vpc'],
supported_platforms=['linux'],
)
with iotests.FilePath('image') as img, \
iotests.FilePath('socket', base_dir=iotests.sock_dir) as socket, \
iotests.VM() as vm:
iotests.qemu_img('create', '-f', iotests.imgfmt, img, '64M')
iotests.qemu_io_log('-f', iotests.imgfmt, '-c', 'write -P 0x11 0 4k', img)
iotests.log('=== Launch VM ===')
virtio_scsi_device = iotests.get_virtio_scsi_device()
vm.add_object('iothread,id=iothread0')
vm.add_blockdev(f'file,filename={img},node-name=file')
vm.add_blockdev(f'{iotests.imgfmt},file=file,node-name=fmt')
vm.add_blockdev('raw,file=file,node-name=ro,read-only=on')
vm.add_device(f'id=scsi0,driver={virtio_scsi_device},iothread=iothread0')
vm.launch()
vm.qmp_log('nbd-server-start',
addr={'type': 'unix', 'data': {'path': socket}},
filters=(iotests.filter_qmp_testfiles, ))
vm.qmp_log('query-block-exports')
iotests.log('\n=== Create a read-only NBD export ===')
vm.qmp_log('block-export-add', id='export0', type='nbd', node_name='fmt')
vm.qmp_log('query-block-exports')
iotests.qemu_nbd_list_log('-k', socket)
iotests.log('\n=== Try a few invalid things ===')
vm.qmp_log('block-export-add', id='#invalid', type='nbd', node_name='fmt')
vm.qmp_log('block-export-add', id='export0', type='nbd', node_name='fmt')
vm.qmp_log('block-export-add', id='export1', type='nbd', node_name='ro',
writable=True)
vm.qmp_log('block-export-del', id='export1')
vm.qmp_log('query-block-exports')
iotests.log('\n=== Move export to an iothread ===')
vm.qmp_log('device_add', id='sda', driver='scsi-hd', drive='fmt')
vm.qmp_log('query-block-exports')
iotests.qemu_nbd_list_log('-k', socket)
iotests.log('\n=== Add a writable export ===')
# This fails because share-rw=off
vm.qmp_log('block-export-add', id='export1', type='nbd', node_name='fmt',
name='export1', writable=True, writethrough=True,
description='This is the writable second export')
vm.qmp_log('device_del', id='sda')
event = vm.event_wait(name="DEVICE_DELETED",
match={'data': {'device': 'sda'}})
iotests.log(event, filters=[iotests.filter_qmp_event])
vm.qmp_log('device_add', id='sda', driver='scsi-hd', drive='fmt',
share_rw=True)
# Now it should work
vm.qmp_log('block-export-add', id='export1', type='nbd', node_name='fmt',
name='export1', writable=True, writethrough=True,
description='This is the writable second export')
vm.qmp_log('query-block-exports')
iotests.qemu_nbd_list_log('-k', socket)
iotests.log('\n=== Connect qemu-io to export1, try removing exports ===')
nbd_url = f'nbd+unix:///export1?socket={socket}'
qemu_io = iotests.QemuIoInteractive('-f', 'raw', nbd_url)
iotests.log(qemu_io.cmd('read -P 0x11 0 4k'),
filters=[iotests.filter_qemu_io])
iotests.log(qemu_io.cmd('write -P 0x22 4k 4k'),
filters=[iotests.filter_qemu_io])
vm.qmp_log('block-export-del', id='export1')
vm.qmp_log('block-export-del', id='export0')
iotests.log(vm.get_qmp_events_filtered())
qemu_io.close()
vm.qmp_log('query-block-exports')
iotests.qemu_nbd_list_log('-k', socket)
iotests.log('\n=== Connect qemu-io again, try force removing ===')
qemu_io = iotests.QemuIoInteractive('-f', 'raw', nbd_url)
vm.qmp_log('block-export-del', id='export1')
vm.qmp_log('block-export-del', id='export1', mode='hard')
# This should fail now
iotests.log(qemu_io.cmd('read -P 0x11 0 4k'))
qemu_io.close()
vm.qmp_log('query-block-exports')
iotests.qemu_nbd_list_log('-k', socket)
iotests.log('\n=== Shut down QEMU ===')
vm.shutdown()

124
tests/qemu-iotests/307.out Normal file
View File

@ -0,0 +1,124 @@
wrote 4096/4096 bytes at offset 0
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
=== Launch VM ===
{"execute": "nbd-server-start", "arguments": {"addr": {"data": {"path": "SOCK_DIR/PID-socket"}, "type": "unix"}}}
{"return": {}}
{"execute": "query-block-exports", "arguments": {}}
{"return": []}
=== Create a read-only NBD export ===
{"execute": "block-export-add", "arguments": {"id": "export0", "node-name": "fmt", "type": "nbd"}}
{"return": {}}
{"execute": "query-block-exports", "arguments": {}}
{"return": [{"id": "export0", "node-name": "fmt", "shutting-down": false, "type": "nbd"}]}
exports available: 1
export: 'fmt'
size: 67108864
flags: 0x58f ( readonly flush fua df multi cache )
min block: XXX
opt block: XXX
max block: XXX
available meta contexts: 1
base:allocation
=== Try a few invalid things ===
{"execute": "block-export-add", "arguments": {"id": "#invalid", "node-name": "fmt", "type": "nbd"}}
{"error": {"class": "GenericError", "desc": "Invalid block export id"}}
{"execute": "block-export-add", "arguments": {"id": "export0", "node-name": "fmt", "type": "nbd"}}
{"error": {"class": "GenericError", "desc": "Block export id 'export0' is already in use"}}
{"execute": "block-export-add", "arguments": {"id": "export1", "node-name": "ro", "type": "nbd", "writable": true}}
{"error": {"class": "GenericError", "desc": "Cannot export read-only node as writable"}}
{"execute": "block-export-del", "arguments": {"id": "export1"}}
{"error": {"class": "GenericError", "desc": "Export 'export1' is not found"}}
{"execute": "query-block-exports", "arguments": {}}
{"return": [{"id": "export0", "node-name": "fmt", "shutting-down": false, "type": "nbd"}]}
=== Move export to an iothread ===
{"execute": "device_add", "arguments": {"drive": "fmt", "driver": "scsi-hd", "id": "sda"}}
{"return": {}}
{"execute": "query-block-exports", "arguments": {}}
{"return": [{"id": "export0", "node-name": "fmt", "shutting-down": false, "type": "nbd"}]}
exports available: 1
export: 'fmt'
size: 67108864
flags: 0x58f ( readonly flush fua df multi cache )
min block: XXX
opt block: XXX
max block: XXX
available meta contexts: 1
base:allocation
=== Add a writable export ===
{"execute": "block-export-add", "arguments": {"description": "This is the writable second export", "id": "export1", "name": "export1", "node-name": "fmt", "type": "nbd", "writable": true, "writethrough": true}}
{"error": {"class": "GenericError", "desc": "Conflicts with use by sda as 'root', which does not allow 'write' on fmt"}}
{"execute": "device_del", "arguments": {"id": "sda"}}
{"return": {}}
{"data": {"device": "sda", "path": "/machine/peripheral/sda"}, "event": "DEVICE_DELETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"execute": "device_add", "arguments": {"drive": "fmt", "driver": "scsi-hd", "id": "sda", "share-rw": true}}
{"return": {}}
{"execute": "block-export-add", "arguments": {"description": "This is the writable second export", "id": "export1", "name": "export1", "node-name": "fmt", "type": "nbd", "writable": true, "writethrough": true}}
{"return": {}}
{"execute": "query-block-exports", "arguments": {}}
{"return": [{"id": "export1", "node-name": "fmt", "shutting-down": false, "type": "nbd"}, {"id": "export0", "node-name": "fmt", "shutting-down": false, "type": "nbd"}]}
exports available: 2
export: 'fmt'
size: 67108864
flags: 0x58f ( readonly flush fua df multi cache )
min block: XXX
opt block: XXX
max block: XXX
available meta contexts: 1
base:allocation
export: 'export1'
description: This is the writable second export
size: 67108864
flags: 0xced ( flush fua trim zeroes df cache fast-zero )
min block: XXX
opt block: XXX
max block: XXX
available meta contexts: 1
base:allocation
=== Connect qemu-io to export1, try removing exports ===
read 4096/4096 bytes at offset 0
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
wrote 4096/4096 bytes at offset 4096
4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{"execute": "block-export-del", "arguments": {"id": "export1"}}
{"error": {"class": "GenericError", "desc": "export 'export1' still in use"}}
{"execute": "block-export-del", "arguments": {"id": "export0"}}
{"return": {}}
[{"data": {"id": "export0"}, "event": "BLOCK_EXPORT_DELETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}]
{"execute": "query-block-exports", "arguments": {}}
{"return": [{"id": "export1", "node-name": "fmt", "shutting-down": false, "type": "nbd"}]}
exports available: 1
export: 'export1'
description: This is the writable second export
size: 67108864
flags: 0xced ( flush fua trim zeroes df cache fast-zero )
min block: XXX
opt block: XXX
max block: XXX
available meta contexts: 1
base:allocation
=== Connect qemu-io again, try force removing ===
{"execute": "block-export-del", "arguments": {"id": "export1"}}
{"error": {"class": "GenericError", "desc": "export 'export1' still in use"}}
{"execute": "block-export-del", "arguments": {"id": "export1", "mode": "hard"}}
{"return": {}}
read failed: Input/output error
{"execute": "query-block-exports", "arguments": {}}
{"return": []}
exports available: 0
=== Shut down QEMU ===

View File

@ -314,3 +314,4 @@
303 rw quick 303 rw quick
304 rw quick 304 rw quick
305 rw quick 305 rw quick
307 rw quick export

View File

@ -65,7 +65,8 @@
qemu_io_args_no_fmt += \ qemu_io_args_no_fmt += \
os.environ['QEMU_IO_OPTIONS_NO_FMT'].strip().split(' ') os.environ['QEMU_IO_OPTIONS_NO_FMT'].strip().split(' ')
qemu_nbd_args = [os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')] qemu_nbd_prog = os.environ.get('QEMU_NBD_PROG', 'qemu-nbd')
qemu_nbd_args = [qemu_nbd_prog]
if os.environ.get('QEMU_NBD_OPTIONS'): if os.environ.get('QEMU_NBD_OPTIONS'):
qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ') qemu_nbd_args += os.environ['QEMU_NBD_OPTIONS'].strip().split(' ')
@ -88,20 +89,29 @@
luks_default_key_secret_opt = 'key-secret=keysec0' luks_default_key_secret_opt = 'key-secret=keysec0'
def qemu_tool_pipe_and_status(tool: str, args: Sequence[str],
connect_stderr: bool = True) -> Tuple[str, int]:
"""
Run a tool and return both its output and its exit code
"""
stderr = subprocess.STDOUT if connect_stderr else None
subp = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=stderr,
universal_newlines=True)
output = subp.communicate()[0]
if subp.returncode < 0:
sys.stderr.write('%s received signal %i: %s\n'
% (tool, -subp.returncode,
' '.join(qemu_img_args + list(args))))
return (output, subp.returncode)
def qemu_img_pipe_and_status(*args: str) -> Tuple[str, int]: def qemu_img_pipe_and_status(*args: str) -> Tuple[str, int]:
""" """
Run qemu-img and return both its output and its exit code Run qemu-img and return both its output and its exit code
""" """
subp = subprocess.Popen(qemu_img_args + list(args), full_args = qemu_img_args + list(args)
stdout=subprocess.PIPE, return qemu_tool_pipe_and_status('qemu-img', full_args)
stderr=subprocess.STDOUT,
universal_newlines=True)
output = subp.communicate()[0]
if subp.returncode < 0:
sys.stderr.write('qemu-img received signal %i: %s\n'
% (-subp.returncode,
' '.join(qemu_img_args + list(args))))
return (output, subp.returncode)
def qemu_img(*args: str) -> int: def qemu_img(*args: str) -> int:
'''Run qemu-img and return the exit code''' '''Run qemu-img and return the exit code'''
@ -263,19 +273,20 @@ def qemu_nbd(*args):
'''Run qemu-nbd in daemon mode and return the parent's exit code''' '''Run qemu-nbd in daemon mode and return the parent's exit code'''
return subprocess.call(qemu_nbd_args + ['--fork'] + list(args)) return subprocess.call(qemu_nbd_args + ['--fork'] + list(args))
def qemu_nbd_early_pipe(*args): def qemu_nbd_early_pipe(*args: str) -> Tuple[int, str]:
'''Run qemu-nbd in daemon mode and return both the parent's exit code '''Run qemu-nbd in daemon mode and return both the parent's exit code
and its output in case of an error''' and its output in case of an error'''
subp = subprocess.Popen(qemu_nbd_args + ['--fork'] + list(args), full_args = qemu_nbd_args + ['--fork'] + list(args)
stdout=subprocess.PIPE, output, returncode = qemu_tool_pipe_and_status('qemu-nbd', full_args,
universal_newlines=True) connect_stderr=False)
output = subp.communicate()[0] return returncode, output if returncode else ''
if subp.returncode < 0:
sys.stderr.write('qemu-nbd received signal %i: %s\n' %
(-subp.returncode,
' '.join(qemu_nbd_args + ['--fork'] + list(args))))
return subp.returncode, output if subp.returncode else '' def qemu_nbd_list_log(*args: str) -> str:
'''Run qemu-nbd to list remote exports'''
full_args = [qemu_nbd_prog, '-L'] + list(args)
output, _ = qemu_tool_pipe_and_status('qemu-nbd', full_args)
log(output, filters=[filter_testfiles, filter_nbd_exports])
return output
@contextmanager @contextmanager
def qemu_nbd_popen(*args): def qemu_nbd_popen(*args):
@ -410,6 +421,9 @@ def _filter(_key, value):
return value return value
return filter_qmp(qmsg, _filter) return filter_qmp(qmsg, _filter)
def filter_nbd_exports(output: str) -> str:
return re.sub(r'((min|opt|max) block): [0-9]+', r'\1: XXX', output)
Msg = TypeVar('Msg', Dict[str, Any], List[Any], str) Msg = TypeVar('Msg', Dict[str, Any], List[Any], str)
@ -1048,16 +1062,12 @@ def case_notrun(reason):
def _verify_image_format(supported_fmts: Sequence[str] = (), def _verify_image_format(supported_fmts: Sequence[str] = (),
unsupported_fmts: Sequence[str] = ()) -> None: unsupported_fmts: Sequence[str] = ()) -> None:
assert not (supported_fmts and unsupported_fmts)
if 'generic' in supported_fmts and \ if 'generic' in supported_fmts and \
os.environ.get('IMGFMT_GENERIC', 'true') == 'true': os.environ.get('IMGFMT_GENERIC', 'true') == 'true':
# similar to # similar to
# _supported_fmt generic # _supported_fmt generic
# for bash tests # for bash tests
if imgfmt == 'luks': supported_fmts = ()
verify_working_luks()
return
not_sup = supported_fmts and (imgfmt not in supported_fmts) not_sup = supported_fmts and (imgfmt not in supported_fmts)
if not_sup or (imgfmt in unsupported_fmts): if not_sup or (imgfmt in unsupported_fmts):
@ -1141,20 +1151,14 @@ def verify_working_luks():
if not working: if not working:
notrun(reason) notrun(reason)
def qemu_pipe(*args): def qemu_pipe(*args: str) -> str:
""" """
Run qemu with an option to print something and exit (e.g. a help option). Run qemu with an option to print something and exit (e.g. a help option).
:return: QEMU's stdout output. :return: QEMU's stdout output.
""" """
args = [qemu_prog] + qemu_opts + list(args) full_args = [qemu_prog] + qemu_opts + list(args)
subp = subprocess.Popen(args, stdout=subprocess.PIPE, output, _ = qemu_tool_pipe_and_status('qemu', full_args)
stderr=subprocess.STDOUT,
universal_newlines=True)
output = subp.communicate()[0]
if subp.returncode < 0:
sys.stderr.write('qemu received signal %i: %s\n' %
(-subp.returncode, ' '.join(args)))
return output return output
def supported_formats(read_only=False): def supported_formats(read_only=False):