mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
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:
@@ -4462,7 +4462,7 @@ static void bdrv_close(BlockDriverState *bs)
|
||||
void bdrv_close_all(void)
|
||||
{
|
||||
assert(job_next(NULL) == NULL);
|
||||
nbd_export_close_all();
|
||||
blk_exp_close_all();
|
||||
|
||||
/* Drop references from requests still in flight, such as canceled block
|
||||
* 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'))
|
||||
|
||||
softmmu_ss.add(files('qapi-sysemu.c'))
|
||||
|
||||
subdir('export')
|
||||
subdir('monitor')
|
||||
|
||||
modules += {'block': block_modules}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "sysemu/blockdev.h"
|
||||
#include "qapi/qapi-commands-block.h"
|
||||
#include "qapi/qapi-commands-block-export.h"
|
||||
#include "qapi/qmp/qdict.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
@@ -397,7 +398,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
|
||||
Error *local_err = NULL;
|
||||
BlockInfoList *block_list, *info;
|
||||
SocketAddress *addr;
|
||||
BlockExportNbd export;
|
||||
NbdServerAddOptions export;
|
||||
|
||||
if (writable && !all) {
|
||||
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;
|
||||
}
|
||||
|
||||
nbd_server_start(addr, NULL, NULL, &local_err);
|
||||
nbd_server_start(addr, NULL, NULL, 0, &local_err);
|
||||
qapi_free_SocketAddress(addr);
|
||||
if (local_err != NULL) {
|
||||
goto exit;
|
||||
@@ -430,7 +431,7 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
|
||||
continue;
|
||||
}
|
||||
|
||||
export = (BlockExportNbd) {
|
||||
export = (NbdServerAddOptions) {
|
||||
.device = info->value->device,
|
||||
.has_writable = true,
|
||||
.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);
|
||||
Error *local_err = NULL;
|
||||
|
||||
BlockExportNbd export = {
|
||||
NbdServerAddOptions export = {
|
||||
.device = (char *) device,
|
||||
.has_name = !!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);
|
||||
Error *err = NULL;
|
||||
|
||||
/* Rely on NBD_SERVER_REMOVE_MODE_SAFE being the default */
|
||||
qmp_nbd_server_remove(name, force, NBD_SERVER_REMOVE_MODE_HARD, &err);
|
||||
/* Rely on BLOCK_EXPORT_REMOVE_MODE_SAFE being the default */
|
||||
qmp_nbd_server_remove(name, force, BLOCK_EXPORT_REMOVE_MODE_HARD, &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,
|
||||
s->l1_table_offset + 8 * l1_start_index, bufsize, false);
|
||||
s->l1_table_offset + L1E_SIZE * l1_start_index, bufsize, false);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
BLKDBG_EVENT(bs->file, BLKDBG_L1_UPDATE);
|
||||
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);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
|
||||
+1
-1
@@ -740,7 +740,7 @@ static coroutine_fn void reconnect_to_sdog(void *opaque)
|
||||
if (s->fd < 0) {
|
||||
trace_sheepdog_reconnect_to_sdog();
|
||||
error_report_err(local_err);
|
||||
qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 1000000000ULL);
|
||||
qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, NANOSECONDS_PER_SECOND);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+92
-81
@@ -14,7 +14,7 @@
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "hw/block/block.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qapi-commands-block.h"
|
||||
#include "qapi/qapi-commands-block-export.h"
|
||||
#include "block/nbd.h"
|
||||
#include "io/channel-socket.h"
|
||||
#include "io/net-listener.h"
|
||||
@@ -23,23 +23,52 @@ typedef struct NBDServerData {
|
||||
QIONetListener *listener;
|
||||
QCryptoTLSCreds *tlscreds;
|
||||
char *tlsauthz;
|
||||
uint32_t max_connections;
|
||||
uint32_t connections;
|
||||
} NBDServerData;
|
||||
|
||||
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)
|
||||
{
|
||||
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,
|
||||
gpointer opaque)
|
||||
{
|
||||
nbd_server->connections++;
|
||||
nbd_update_server_watch(nbd_server);
|
||||
|
||||
qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
|
||||
nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz,
|
||||
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)
|
||||
{
|
||||
@@ -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,
|
||||
const char *tls_authz, Error **errp)
|
||||
const char *tls_authz, uint32_t max_connections,
|
||||
Error **errp)
|
||||
{
|
||||
if (nbd_server) {
|
||||
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->max_connections = max_connections;
|
||||
nbd_server->listener = qio_net_listener_new();
|
||||
|
||||
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);
|
||||
|
||||
qio_net_listener_set_client_func(nbd_server->listener,
|
||||
nbd_accept,
|
||||
NULL,
|
||||
NULL);
|
||||
nbd_update_server_watch(nbd_server);
|
||||
|
||||
return;
|
||||
|
||||
@@ -134,117 +162,100 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
|
||||
|
||||
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,
|
||||
bool has_tls_creds, const char *tls_creds,
|
||||
bool has_tls_authz, const char *tls_authz,
|
||||
bool has_max_connections, uint32_t max_connections,
|
||||
Error **errp)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
NBDExport *exp;
|
||||
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);
|
||||
BlockExportOptions *export_opts;
|
||||
|
||||
bs = bdrv_lookup_bs(arg->device, arg->device, errp);
|
||||
if (!bs) {
|
||||
return;
|
||||
}
|
||||
|
||||
aio_context = bdrv_get_aio_context(bs);
|
||||
aio_context_acquire(aio_context);
|
||||
len = bdrv_getlength(bs);
|
||||
if (len < 0) {
|
||||
error_setg_errno(errp, -len,
|
||||
"Failed to determine the NBD export's length");
|
||||
goto out;
|
||||
/*
|
||||
* block-export-add would default to the node-name, but we may have to use
|
||||
* the device name as a default here for compatibility.
|
||||
*/
|
||||
if (!arg->has_name) {
|
||||
arg->name = arg->device;
|
||||
}
|
||||
|
||||
if (!arg->has_writable) {
|
||||
arg->writable = false;
|
||||
}
|
||||
export_opts = g_new(BlockExportOptions, 1);
|
||||
*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)) {
|
||||
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,
|
||||
!arg->writable, !arg->writable,
|
||||
NULL, false, on_eject_blk, errp);
|
||||
if (!exp) {
|
||||
goto out;
|
||||
export = blk_exp_add(export_opts, errp);
|
||||
if (!export) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* the strong reference that is @exp. */
|
||||
nbd_export_put(exp);
|
||||
/*
|
||||
* nbd-server-add removes the export when the named BlockBackend used for
|
||||
* @device goes away.
|
||||
*/
|
||||
on_eject_blk = blk_by_name(arg->device);
|
||||
if (on_eject_blk) {
|
||||
nbd_export_set_on_eject_blk(export, on_eject_blk);
|
||||
}
|
||||
|
||||
out:
|
||||
aio_context_release(aio_context);
|
||||
fail:
|
||||
qapi_free_BlockExportOptions(export_opts);
|
||||
}
|
||||
|
||||
void qmp_nbd_server_remove(const char *name,
|
||||
bool has_mode, NbdServerRemoveMode mode,
|
||||
bool has_mode, BlockExportRemoveMode mode,
|
||||
Error **errp)
|
||||
{
|
||||
NBDExport *exp;
|
||||
AioContext *aio_context;
|
||||
BlockExport *exp;
|
||||
|
||||
if (!nbd_server) {
|
||||
error_setg(errp, "NBD server not running");
|
||||
exp = blk_exp_find(name);
|
||||
if (exp && exp->drv->type != BLOCK_EXPORT_TYPE_NBD) {
|
||||
error_setg(errp, "Block export '%s' is not an NBD export", name);
|
||||
return;
|
||||
}
|
||||
|
||||
exp = nbd_export_find(name);
|
||||
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);
|
||||
qmp_block_export_del(name, has_mode, mode, errp);
|
||||
}
|
||||
|
||||
void qmp_nbd_server_stop(Error **errp)
|
||||
@@ -254,7 +265,7 @@ void qmp_nbd_server_stop(Error **errp)
|
||||
return;
|
||||
}
|
||||
|
||||
nbd_export_close_all();
|
||||
blk_exp_close_all_type(BLOCK_EXPORT_TYPE_NBD);
|
||||
|
||||
nbd_server_free(nbd_server);
|
||||
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
|
||||
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
|
||||
-------------------------------------
|
||||
|
||||
|
||||
+107
-1
@@ -1,6 +1,6 @@
|
||||
The QEMU throttling infrastructure
|
||||
==================================
|
||||
Copyright (C) 2016 Igalia, S.L.
|
||||
Copyright (C) 2016,2020 Igalia, S.L.
|
||||
Author: Alberto Garcia <berto@igalia.com>
|
||||
|
||||
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
|
||||
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
|
||||
+8
-14
@@ -20,11 +20,13 @@
|
||||
#ifndef NBD_H
|
||||
#define NBD_H
|
||||
|
||||
#include "qapi/qapi-types-block.h"
|
||||
#include "block/export.h"
|
||||
#include "io/channel-socket.h"
|
||||
#include "crypto/tlscreds.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
extern const BlockExportDriver blk_exp_nbd;
|
||||
|
||||
/* Handshake phase structs - this struct is passed on the wire */
|
||||
|
||||
struct NBDOption {
|
||||
@@ -328,21 +330,10 @@ int nbd_errno_to_system_errno(int err);
|
||||
typedef struct NBDExport NBDExport;
|
||||
typedef struct NBDClient NBDClient;
|
||||
|
||||
NBDExport *nbd_export_new(BlockDriverState *bs, uint64_t dev_offset,
|
||||
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);
|
||||
void nbd_export_set_on_eject_blk(BlockExport *exp, BlockBackend *blk);
|
||||
|
||||
AioContext *nbd_export_aio_context(NBDExport *exp);
|
||||
NBDExport *nbd_export_find(const char *name);
|
||||
void nbd_export_close_all(void);
|
||||
|
||||
void nbd_client_new(QIOChannelSocket *sioc,
|
||||
QCryptoTLSCreds *tlscreds,
|
||||
@@ -351,8 +342,11 @@ void nbd_client_new(QIOChannelSocket *sioc,
|
||||
void nbd_client_get(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,
|
||||
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);
|
||||
|
||||
/* nbd_read
|
||||
|
||||
+1
-1
@@ -970,6 +970,7 @@ subdir('dump')
|
||||
|
||||
block_ss.add(files(
|
||||
'block.c',
|
||||
'blockdev-nbd.c',
|
||||
'blockjob.c',
|
||||
'job.c',
|
||||
'qemu-io-cmds.c',
|
||||
@@ -982,7 +983,6 @@ subdir('block')
|
||||
|
||||
blockdev_ss.add(files(
|
||||
'blockdev.c',
|
||||
'blockdev-nbd.c',
|
||||
'iothread.c',
|
||||
'job-qmp.c',
|
||||
))
|
||||
|
||||
+134
-181
File diff suppressed because it is too large
Load Diff
@@ -5194,172 +5194,6 @@
|
||||
'iothread': 'StrOrNull',
|
||||
'*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:
|
||||
#
|
||||
|
||||
@@ -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'] }
|
||||
+3
-1
@@ -17,8 +17,9 @@ qapi_all_modules = [
|
||||
'acpi',
|
||||
'audio',
|
||||
'authz',
|
||||
'block-core',
|
||||
'block',
|
||||
'block-core',
|
||||
'block-export',
|
||||
'char',
|
||||
'common',
|
||||
'control',
|
||||
@@ -49,6 +50,7 @@ qapi_all_modules = [
|
||||
|
||||
qapi_storage_daemon_modules = [
|
||||
'block-core',
|
||||
'block-export',
|
||||
'char',
|
||||
'common',
|
||||
'control',
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
{ 'include': 'run-state.json' }
|
||||
{ 'include': 'crypto.json' }
|
||||
{ 'include': 'block.json' }
|
||||
{ 'include': 'block-export.json' }
|
||||
{ 'include': 'char.json' }
|
||||
{ 'include': 'dump.json' }
|
||||
{ 'include': 'job.json' }
|
||||
|
||||
+2
-9
@@ -2383,14 +2383,7 @@ static const cmdinfo_t sleep_cmd = {
|
||||
|
||||
static void help_oneline(const char *cmd, const cmdinfo_t *ct)
|
||||
{
|
||||
if (cmd) {
|
||||
printf("%s ", cmd);
|
||||
} else {
|
||||
printf("%s ", ct->name);
|
||||
if (ct->altname) {
|
||||
printf("(or %s) ", ct->altname);
|
||||
}
|
||||
}
|
||||
printf("%s ", cmd);
|
||||
|
||||
if (ct->args) {
|
||||
printf("%s ", ct->args);
|
||||
@@ -2420,7 +2413,7 @@ static int help_f(BlockBackend *blk, int argc, char **argv)
|
||||
{
|
||||
const cmdinfo_t *ct;
|
||||
|
||||
if (argc == 1) {
|
||||
if (argc < 2) {
|
||||
help_all();
|
||||
return 0;
|
||||
}
|
||||
|
||||
+36
-29
@@ -65,12 +65,11 @@
|
||||
|
||||
#define MBR_SIZE 512
|
||||
|
||||
static NBDExport *export;
|
||||
static int verbose;
|
||||
static char *srcpath;
|
||||
static SocketAddress *saddr;
|
||||
static int persistent = 0;
|
||||
static enum { RUNNING, TERMINATE, TERMINATING, TERMINATED } state;
|
||||
static enum { RUNNING, TERMINATE, TERMINATED } state;
|
||||
static int shared = 1;
|
||||
static int nb_fds;
|
||||
static QIONetListener *server;
|
||||
@@ -332,12 +331,6 @@ static int nbd_can_accept(void)
|
||||
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_client_closed(NBDClient *client, bool negotiated)
|
||||
@@ -524,7 +517,6 @@ int main(int argc, char **argv)
|
||||
const char *port = NULL;
|
||||
char *sockpath = NULL;
|
||||
char *device = NULL;
|
||||
int64_t fd_size;
|
||||
QemuOpts *sn_opts = NULL;
|
||||
const char *sn_id_or_name = NULL;
|
||||
const char *sopt = "hVb:o:p:rsnc:dvk:e:f:tl:x:T:D:B:L";
|
||||
@@ -587,6 +579,7 @@ int main(int argc, char **argv)
|
||||
int old_stderr = -1;
|
||||
unsigned socket_activation;
|
||||
const char *pid_file_name = NULL;
|
||||
BlockExportOptions *export_opts;
|
||||
|
||||
#if HAVE_NBD_DEVICE
|
||||
/* 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);
|
||||
|
||||
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);
|
||||
|
||||
if (sn_opts) {
|
||||
@@ -1054,24 +1058,29 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
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) {
|
||||
error_report("Offset (%" PRIu64 ") has to be smaller than the image "
|
||||
"size (%" PRId64 ")", dev_offset, fd_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
fd_size -= dev_offset;
|
||||
nbd_server_is_qemu_nbd(true);
|
||||
|
||||
export = nbd_export_new(bs, dev_offset, fd_size, export_name,
|
||||
export_description, bitmap, readonly, shared > 1,
|
||||
nbd_export_closed, writethrough, NULL,
|
||||
&error_fatal);
|
||||
export_opts = g_new(BlockExportOptions, 1);
|
||||
*export_opts = (BlockExportOptions) {
|
||||
.type = BLOCK_EXPORT_TYPE_NBD,
|
||||
.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 HAVE_NBD_DEVICE
|
||||
@@ -1111,10 +1120,8 @@ int main(int argc, char **argv)
|
||||
do {
|
||||
main_loop_wait(false);
|
||||
if (state == TERMINATE) {
|
||||
state = TERMINATING;
|
||||
nbd_export_close(export);
|
||||
nbd_export_put(export);
|
||||
export = NULL;
|
||||
blk_exp_close_all();
|
||||
state = TERMINATED;
|
||||
}
|
||||
} while (state != TERMINATED);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user