mirror of https://gitee.com/openkylin/qemu.git
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:
commit
469e72ab7d
2
block.c
2
block.c
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
block_ss.add(files('export.c'))
|
|
@ -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}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
173
blockdev-nbd.c
173
blockdev-nbd.c
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
))
|
))
|
||||||
|
|
315
nbd/server.c
315
nbd/server.c
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
#
|
#
|
||||||
|
|
|
@ -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'] }
|
|
@ -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',
|
||||||
|
|
|
@ -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' }
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
65
qemu-nbd.c
65
qemu-nbd.c
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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' }
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
|
@ -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' }
|
||||||
|
|
|
@ -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": {}}
|
||||||
|
|
|
@ -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()
|
|
@ -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 ===
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue