Merge tag 'for-upstream' of https://repo.or.cz/qemu/kevin into staging

Block layer patches

- Managing inactive nodes (enables QSD migration with shared storage)
- Fix swapped values for BLOCK_IO_ERROR 'device' and 'qom-path'
- vpc: Read images exported from Azure correctly
- scripts/qemu-gdb: Support coroutine dumps in coredumps
- Minor cleanups

# -----BEGIN PGP SIGNATURE-----
#
# iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmek34IRHGt3b2xmQHJl
# ZGhhdC5jb20ACgkQfwmycsiPL9bDpxAAnTvwmdazAXG0g9GzqvrEB/+6rStjAsqE
# 9MTWV4WxyN41d0RXxN8CYKb8CXSiTRyw6r3CSGNYEI2eShe9e934PriSkZm41HyX
# n9Yh5YxqGZqitzvPtx62Ii/1KG+PcjQbfHuK1p4+rlKa0yQ2eGlio1JIIrZrCkBZ
# ikZcQUrhIyD0XV8hTQ2+Ysa+ZN6itjnlTQIG3gS3m8f8WR7kyUXD8YFMQFJFyjVx
# NrAIpLnc/ln9+5PZR9tje8U7XEn2KCgI5pgGaQnrd0h0G1H4ig8ogzYYnKTLhjU/
# AmQpS8np8Tyg6S1UZTiekEq0VuAhThEQc5b3sGbmHWH/R2ABMStyf18oCBAkPzZ7
# s6h+3XzTKKY2Q5Q3ZG/ANkUJjTNBhdj1fcaARvbSWsqsuk5CWX/I3jzvgihFtCSs
# eGu+b/bLeW6P7hu4qPHBcgLHuB1Fc7Rd2t4BoIGM1wcO2CeC9DzUKOiIMZOEJIh0
# GGqCkEWDHgckDTakD4/vSqm0UDKt6FSlQC9ga/ILBY3IB5HpHoArY58selymy28i
# X7MgAvbjdsmNuUuXDZZOiObcFt3j8jlmwPJpPyzXPQIiPX1RXeBPRhVAEeZCKn6Z
# tfHr72SJdMeVOGXVTvOrJ2iW+4g03rPdmkDFCUhpOwo62RODq7ahvCIXsNf3nEFR
# rSB3T1M/8EM=
# =iQLP
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 06 Feb 2025 11:12:50 EST
# 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

* tag 'for-upstream' of https://repo.or.cz/qemu/kevin: (25 commits)
  block: remove unused BLOCK_OP_TYPE_DATAPLANE
  iotests: Add (NBD-based) tests for inactive nodes
  iotests: Add qsd-migrate case
  iotests: Add filter_qtest()
  nbd/server: Support inactive nodes
  block/export: Add option to allow export of inactive nodes
  block: Drain nodes before inactivating them
  block/export: Don't ignore image activation error in blk_exp_add()
  block: Support inactive nodes in blk_insert_bs()
  block: Add blockdev-set-active QMP command
  block: Add option to create inactive nodes
  block: Fix crash on block_resize on inactive node
  block: Don't attach inactive child to active node
  migration/block-active: Remove global active flag
  block: Inactivate external snapshot overlays when necessary
  block: Allow inactivating already inactive nodes
  block: Add 'active' field to BlockDeviceInfo
  block-backend: Fix argument order when calling 'qapi_event_send_block_io_error()'
  scripts/qemu-gdb: Support coroutine dumps in coredumps
  scripts/qemu-gdb: Simplify fs_base fetching for coroutines
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi
2025-02-10 13:25:36 -05:00
35 changed files with 1133 additions and 166 deletions
+60 -4
View File
@@ -1573,6 +1573,10 @@ static void update_flags_from_options(int *flags, QemuOpts *opts)
if (qemu_opt_get_bool_del(opts, BDRV_OPT_AUTO_READ_ONLY, false)) {
*flags |= BDRV_O_AUTO_RDONLY;
}
if (!qemu_opt_get_bool_del(opts, BDRV_OPT_ACTIVE, true)) {
*flags |= BDRV_O_INACTIVE;
}
}
static void update_options_from_flags(QDict *options, int flags)
@@ -1799,6 +1803,11 @@ QemuOptsList bdrv_runtime_opts = {
.type = QEMU_OPT_BOOL,
.help = "Ignore flush requests",
},
{
.name = BDRV_OPT_ACTIVE,
.type = QEMU_OPT_BOOL,
.help = "Node is activated",
},
{
.name = BDRV_OPT_READ_ONLY,
.type = QEMU_OPT_BOOL,
@@ -3077,6 +3086,13 @@ bdrv_attach_child_common(BlockDriverState *child_bs,
assert(child_class->get_parent_desc);
GLOBAL_STATE_CODE();
if (bdrv_is_inactive(child_bs) && (perm & ~BLK_PERM_CONSISTENT_READ)) {
g_autofree char *perm_names = bdrv_perm_names(perm);
error_setg(errp, "Permission '%s' unavailable on inactive node",
perm_names);
return NULL;
}
new_child = g_new(BdrvChild, 1);
*new_child = (BdrvChild) {
.bs = NULL,
@@ -3183,6 +3199,11 @@ bdrv_attach_child_noperm(BlockDriverState *parent_bs,
child_bs->node_name, child_name, parent_bs->node_name);
return NULL;
}
if (bdrv_is_inactive(child_bs) && !bdrv_is_inactive(parent_bs)) {
error_setg(errp, "Inactive '%s' can't be a %s child of active '%s'",
child_bs->node_name, child_name, parent_bs->node_name);
return NULL;
}
bdrv_get_cumulative_perm(parent_bs, &perm, &shared_perm);
bdrv_child_perm(parent_bs, child_bs, NULL, child_role, NULL,
@@ -6824,6 +6845,10 @@ void bdrv_init_with_whitelist(void)
bdrv_init();
}
bool bdrv_is_inactive(BlockDriverState *bs) {
return bs->open_flags & BDRV_O_INACTIVE;
}
int bdrv_activate(BlockDriverState *bs, Error **errp)
{
BdrvChild *child, *parent;
@@ -6955,7 +6980,8 @@ bdrv_has_bds_parent(BlockDriverState *bs, bool only_active)
return false;
}
static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs)
static int GRAPH_RDLOCK
bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level)
{
BdrvChild *child, *parent;
int ret;
@@ -6973,7 +6999,14 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs)
return 0;
}
assert(!(bs->open_flags & BDRV_O_INACTIVE));
/*
* Inactivating an already inactive node on user request is harmless, but if
* a child is already inactive before its parent, that's bad.
*/
if (bs->open_flags & BDRV_O_INACTIVE) {
assert(top_level);
return 0;
}
/* Inactivate this node */
if (bs->drv->bdrv_inactivate) {
@@ -6999,7 +7032,9 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs)
return -EPERM;
}
bdrv_drained_begin(bs);
bs->open_flags |= BDRV_O_INACTIVE;
bdrv_drained_end(bs);
/*
* Update permissions, they may differ for inactive nodes.
@@ -7010,7 +7045,7 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs)
/* Recursively inactivate children */
QLIST_FOREACH(child, &bs->children, next) {
ret = bdrv_inactivate_recurse(child->bs);
ret = bdrv_inactivate_recurse(child->bs, false);
if (ret < 0) {
return ret;
}
@@ -7019,6 +7054,27 @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs)
return 0;
}
int bdrv_inactivate(BlockDriverState *bs, Error **errp)
{
int ret;
GLOBAL_STATE_CODE();
GRAPH_RDLOCK_GUARD_MAINLOOP();
if (bdrv_has_bds_parent(bs, true)) {
error_setg(errp, "Node has active parent node");
return -EPERM;
}
ret = bdrv_inactivate_recurse(bs, true);
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to inactivate node");
return ret;
}
return 0;
}
int bdrv_inactivate_all(void)
{
BlockDriverState *bs = NULL;
@@ -7035,7 +7091,7 @@ int bdrv_inactivate_all(void)
if (bdrv_has_bds_parent(bs, false)) {
continue;
}
ret = bdrv_inactivate_recurse(bs);
ret = bdrv_inactivate_recurse(bs, true);
if (ret < 0) {
bdrv_next_cleanup(&it);
break;
+23 -9
View File
@@ -253,7 +253,7 @@ static bool blk_can_inactivate(BlockBackend *blk)
* guest. For block job BBs that satisfy this, we can just allow
* it. This is the case for mirror job source, which is required
* by libvirt non-shared block migration. */
if (!(blk->perm & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED))) {
if (!(blk->perm & ~BLK_PERM_CONSISTENT_READ)) {
return true;
}
@@ -900,14 +900,24 @@ void blk_remove_bs(BlockBackend *blk)
int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp)
{
ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
uint64_t perm, shared_perm;
GLOBAL_STATE_CODE();
bdrv_ref(bs);
bdrv_graph_wrlock();
if ((bs->open_flags & BDRV_O_INACTIVE) && blk_can_inactivate(blk)) {
blk->disable_perm = true;
perm = 0;
shared_perm = BLK_PERM_ALL;
} else {
perm = blk->perm;
shared_perm = blk->shared_perm;
}
blk->root = bdrv_root_attach_child(bs, "root", &child_root,
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
blk->perm, blk->shared_perm,
blk, errp);
perm, shared_perm, blk, errp);
bdrv_graph_wrunlock();
if (blk->root == NULL) {
return -EPERM;
@@ -1019,6 +1029,10 @@ DeviceState *blk_get_attached_dev(BlockBackend *blk)
return blk->dev;
}
/*
* The caller is responsible for releasing the value returned
* with g_free() after use.
*/
static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id)
{
DeviceState *dev = blk->dev;
@@ -1033,15 +1047,15 @@ static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id)
return object_get_canonical_path(OBJECT(dev)) ?: g_strdup("");
}
/*
* Return the qdev ID, or if no ID is assigned the QOM path, of the block
* device attached to the BlockBackend.
*/
char *blk_get_attached_dev_id(BlockBackend *blk)
{
return blk_get_attached_dev_id_or_path(blk, true);
}
/*
* The caller is responsible for releasing the value returned
* with g_free() after use.
*/
static char *blk_get_attached_dev_path(BlockBackend *blk)
{
return blk_get_attached_dev_id_or_path(blk, false);
@@ -2134,10 +2148,10 @@ static void send_qmp_error_event(BlockBackend *blk,
{
IoOperationType optype;
BlockDriverState *bs = blk_bs(blk);
g_autofree char *path = blk_get_attached_dev_path(blk);
optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE;
qapi_event_send_block_io_error(blk_name(blk),
blk_get_attached_dev_path(blk),
qapi_event_send_block_io_error(path, blk_name(blk),
bs ? bdrv_get_node_name(bs) : NULL, optype,
action, blk_iostatus_is_enabled(blk),
error == ENOSPC, strerror(error));
+22 -7
View File
@@ -75,6 +75,7 @@ static const BlockExportDriver *blk_exp_find_driver(BlockExportType type)
BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
{
bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread;
bool allow_inactive = export->has_allow_inactive && export->allow_inactive;
const BlockExportDriver *drv;
BlockExport *exp = NULL;
BlockDriverState *bs;
@@ -138,14 +139,25 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
}
}
/*
* 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_graph_rdlock_main_loop();
bdrv_activate(bs, NULL);
if (allow_inactive) {
if (!drv->supports_inactive) {
error_setg(errp, "Export type does not support inactive exports");
bdrv_graph_rdunlock_main_loop();
goto fail;
}
} else {
/*
* 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.
*/
ret = bdrv_activate(bs, errp);
if (ret < 0) {
bdrv_graph_rdunlock_main_loop();
goto fail;
}
}
bdrv_graph_rdunlock_main_loop();
perm = BLK_PERM_CONSISTENT_READ;
@@ -158,6 +170,9 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
if (!fixed_iothread) {
blk_set_allow_aio_context_change(blk, true);
}
if (allow_inactive) {
blk_set_force_allow_inactivate(blk);
}
ret = blk_insert_bs(blk, bs, errp);
if (ret < 0) {
+3 -2
View File
@@ -630,11 +630,12 @@ static void print_block_info(Monitor *mon, BlockInfo *info,
}
if (inserted) {
monitor_printf(mon, ": %s (%s%s%s)\n",
monitor_printf(mon, ": %s (%s%s%s%s)\n",
inserted->file,
inserted->drv,
inserted->ro ? ", read-only" : "",
inserted->encrypted ? ", encrypted" : "");
inserted->encrypted ? ", encrypted" : "",
inserted->active ? "" : ", inactive");
} else {
monitor_printf(mon, ": [not inserted]\n");
}
+1
View File
@@ -63,6 +63,7 @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk,
info->file = g_strdup(bs->filename);
info->ro = bdrv_is_read_only(bs);
info->drv = g_strdup(bs->drv->format_name);
info->active = !bdrv_is_inactive(bs);
info->encrypted = bs->encrypted;
info->cache = g_new(BlockdevCacheInfo, 1);
-1
View File
@@ -576,7 +576,6 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
return;
}
bdrv_op_block_all(top_bs, s->blocker);
bdrv_op_unblock(top_bs, BLOCK_OP_TYPE_DATAPLANE, s->blocker);
bdrv_graph_wrunlock();
+35 -30
View File
@@ -216,6 +216,39 @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts,
}
}
/*
* Microsoft Virtual PC and Microsoft Hyper-V produce and read
* VHD image sizes differently. VPC will rely on CHS geometry,
* while Hyper-V and disk2vhd use the size specified in the footer.
*
* We use a couple of approaches to try and determine the correct method:
* look at the Creator App field, and look for images that have CHS
* geometry that is the maximum value.
*
* If the CHS geometry is the maximum CHS geometry, then we assume that
* the size is the footer->current_size to avoid truncation. Otherwise,
* we follow the table based on footer->creator_app:
*
* Known creator apps:
* 'vpc ' : CHS Virtual PC (uses disk geometry)
* 'qemu' : CHS QEMU (uses disk geometry)
* 'qem2' : current_size QEMU (uses current_size)
* 'win ' : current_size Hyper-V
* 'd2v ' : current_size Disk2vhd
* 'tap\0' : current_size XenServer
* 'CTXS' : current_size XenConverter
* 'wa\0\0': current_size Azure
*
* The user can override the table values via drive options, however
* even with an override we will still use current_size for images
* that have CHS geometry of the maximum size.
*/
static bool vpc_ignore_current_size(VHDFooter *footer)
{
return !strncmp(footer->creator_app, "vpc ", 4) ||
!strncmp(footer->creator_app, "qemu", 4);
}
static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp)
{
@@ -304,36 +337,8 @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags,
bs->total_sectors = (int64_t)
be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl;
/* Microsoft Virtual PC and Microsoft Hyper-V produce and read
* VHD image sizes differently. VPC will rely on CHS geometry,
* while Hyper-V and disk2vhd use the size specified in the footer.
*
* We use a couple of approaches to try and determine the correct method:
* look at the Creator App field, and look for images that have CHS
* geometry that is the maximum value.
*
* If the CHS geometry is the maximum CHS geometry, then we assume that
* the size is the footer->current_size to avoid truncation. Otherwise,
* we follow the table based on footer->creator_app:
*
* Known creator apps:
* 'vpc ' : CHS Virtual PC (uses disk geometry)
* 'qemu' : CHS QEMU (uses disk geometry)
* 'qem2' : current_size QEMU (uses current_size)
* 'win ' : current_size Hyper-V
* 'd2v ' : current_size Disk2vhd
* 'tap\0' : current_size XenServer
* 'CTXS' : current_size XenConverter
*
* The user can override the table values via drive options, however
* even with an override we will still use current_size for images
* that have CHS geometry of the maximum size.
*/
use_chs = (!!strncmp(footer->creator_app, "win ", 4) &&
!!strncmp(footer->creator_app, "qem2", 4) &&
!!strncmp(footer->creator_app, "d2v ", 4) &&
!!strncmp(footer->creator_app, "CTXS", 4) &&
!!memcmp(footer->creator_app, "tap", 4)) || s->force_use_chs;
/* Use CHS or current_size to determine the image size. */
use_chs = vpc_ignore_current_size(footer) || s->force_use_chs;
if (!use_chs || bs->total_sectors == VHD_MAX_GEOMETRY || s->force_use_sz) {
bs->total_sectors = be64_to_cpu(footer->current_size) /
+48
View File
@@ -1497,6 +1497,22 @@ static void external_snapshot_action(TransactionAction *action,
return;
}
/*
* Older QEMU versions have allowed adding an active parent node to an
* inactive child node. This is unsafe in the general case, but there is an
* important use case, which is taking a VM snapshot with migration to file
* and then adding an external snapshot while the VM is still stopped and
* images are inactive. Requiring the user to explicitly create the overlay
* as inactive would break compatibility, so just do it automatically here
* to keep this working.
*/
if (bdrv_is_inactive(state->old_bs) && !bdrv_is_inactive(state->new_bs)) {
ret = bdrv_inactivate(state->new_bs, errp);
if (ret < 0) {
return;
}
}
ret = bdrv_append(state->new_bs, state->old_bs, errp);
if (ret < 0) {
return;
@@ -3455,6 +3471,38 @@ void qmp_blockdev_del(const char *node_name, Error **errp)
bdrv_unref(bs);
}
void qmp_blockdev_set_active(const char *node_name, bool active, Error **errp)
{
int ret;
GLOBAL_STATE_CODE();
GRAPH_RDLOCK_GUARD_MAINLOOP();
if (!node_name) {
if (active) {
bdrv_activate_all(errp);
} else {
ret = bdrv_inactivate_all();
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to inactivate all nodes");
}
}
} else {
BlockDriverState *bs = bdrv_find_node(node_name);
if (!bs) {
error_setg(errp, "Failed to find node with node-name='%s'",
node_name);
return;
}
if (active) {
bdrv_activate(bs, errp);
} else {
bdrv_inactivate(bs, errp);
}
}
}
static BdrvChild * GRAPH_RDLOCK
bdrv_find_child(BlockDriverState *parent_bs, const char *child_name)
{
-2
View File
@@ -539,8 +539,6 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,
goto fail;
}
bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker);
if (!block_job_set_speed(job, speed, errp)) {
goto fail;
}
-9
View File
@@ -1562,15 +1562,6 @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp)
error_setg(errp, "ioeventfd is required for iothread");
return false;
}
/*
* If ioeventfd is (re-)enabled while the guest is running there could
* be block jobs that can conflict.
*/
if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
error_prepend(errp, "cannot start virtio-blk ioeventfd: ");
return false;
}
}
s->vq_aio_context = g_new(AioContext *, conf->num_queues);
-3
View File
@@ -1065,9 +1065,6 @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev,
int ret;
if (s->ctx && !s->dataplane_fenced) {
if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
return;
}
ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp);
if (ret < 0) {
return;
+1 -1
View File
@@ -257,6 +257,7 @@ typedef enum {
#define BDRV_OPT_AUTO_READ_ONLY "auto-read-only"
#define BDRV_OPT_DISCARD "discard"
#define BDRV_OPT_FORCE_SHARE "force-share"
#define BDRV_OPT_ACTIVE "active"
#define BDRV_SECTOR_BITS 9
@@ -355,7 +356,6 @@ typedef enum BlockOpType {
BLOCK_OP_TYPE_CHANGE,
BLOCK_OP_TYPE_COMMIT_SOURCE,
BLOCK_OP_TYPE_COMMIT_TARGET,
BLOCK_OP_TYPE_DATAPLANE,
BLOCK_OP_TYPE_DRIVE_DEL,
BLOCK_OP_TYPE_EJECT,
BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT,
+6
View File
@@ -175,12 +175,18 @@ BlockDriverState * GRAPH_RDLOCK
check_to_replace_node(BlockDriverState *parent_bs, const char *node_name,
Error **errp);
bool GRAPH_RDLOCK bdrv_is_inactive(BlockDriverState *bs);
int no_coroutine_fn GRAPH_RDLOCK
bdrv_activate(BlockDriverState *bs, Error **errp);
int coroutine_fn no_co_wrapper_bdrv_rdlock
bdrv_co_activate(BlockDriverState *bs, Error **errp);
int no_coroutine_fn
bdrv_inactivate(BlockDriverState *bs, Error **errp);
void bdrv_activate_all(Error **errp);
int bdrv_inactivate_all(void);
+3
View File
@@ -29,6 +29,9 @@ typedef struct BlockExportDriver {
*/
size_t instance_size;
/* True if the export type supports running on an inactive node */
bool supports_inactive;
/* Creates and starts a new block export */
int (*create)(BlockExport *, BlockExportOptions *, Error **);
+7
View File
@@ -32,6 +32,13 @@ void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow);
void blk_set_disable_request_queuing(BlockBackend *blk, bool disable);
bool blk_iostatus_is_enabled(const BlockBackend *blk);
/*
* Return the qdev ID, or if no ID is assigned the QOM path,
* of the block device attached to the BlockBackend.
*
* The caller is responsible for releasing the value returned
* with g_free() after use.
*/
char *blk_get_attached_dev_id(BlockBackend *blk);
BlockAIOCB *blk_aio_pwrite_zeroes(BlockBackend *blk, int64_t offset,
-46
View File
@@ -12,51 +12,12 @@
#include "qemu/error-report.h"
#include "trace.h"
/*
* Migration-only cache to remember the block layer activation status.
* Protected by BQL.
*
* We need this because..
*
* - Migration can fail after block devices are invalidated (during
* switchover phase). When that happens, we need to be able to recover
* the block drive status by re-activating them.
*
* - Currently bdrv_inactivate_all() is not safe to be invoked on top of
* invalidated drives (even if bdrv_activate_all() is actually safe to be
* called any time!). It means remembering this could help migration to
* make sure it won't invalidate twice in a row, crashing QEMU. It can
* happen when we migrate a PAUSED VM from host1 to host2, then migrate
* again to host3 without starting it. TODO: a cleaner solution is to
* allow safe invoke of bdrv_inactivate_all() at anytime, like
* bdrv_activate_all().
*
* For freshly started QEMU, the flag is initialized to TRUE reflecting the
* scenario where QEMU owns block device ownerships.
*
* For incoming QEMU taking a migration stream, the flag is initialized to
* FALSE reflecting that the incoming side doesn't own the block devices,
* not until switchover happens.
*/
static bool migration_block_active;
/* Setup the disk activation status */
void migration_block_active_setup(bool active)
{
migration_block_active = active;
}
bool migration_block_activate(Error **errp)
{
ERRP_GUARD();
assert(bql_locked());
if (migration_block_active) {
trace_migration_block_activation("active-skipped");
return true;
}
trace_migration_block_activation("active");
bdrv_activate_all(errp);
@@ -65,7 +26,6 @@ bool migration_block_activate(Error **errp)
return false;
}
migration_block_active = true;
return true;
}
@@ -75,11 +35,6 @@ bool migration_block_inactivate(void)
assert(bql_locked());
if (!migration_block_active) {
trace_migration_block_activation("inactive-skipped");
return true;
}
trace_migration_block_activation("inactive");
ret = bdrv_inactivate_all();
@@ -89,6 +44,5 @@ bool migration_block_inactivate(void)
return false;
}
migration_block_active = false;
return true;
}
-8
View File
@@ -1895,12 +1895,6 @@ void qmp_migrate_incoming(const char *uri, bool has_channels,
return;
}
/*
* Newly setup incoming QEMU. Mark the block active state to reflect
* that the src currently owns the disks.
*/
migration_block_active_setup(false);
once = false;
}
@@ -3992,8 +3986,6 @@ static void migration_instance_init(Object *obj)
ms->state = MIGRATION_STATUS_NONE;
ms->mbps = -1;
ms->pages_per_second = -1;
/* Freshly started QEMU owns all the block devices */
migration_block_active_setup(true);
qemu_sem_init(&ms->pause_sem, 0);
qemu_mutex_init(&ms->error_mutex);
-3
View File
@@ -554,7 +554,4 @@ void migration_bitmap_sync_precopy(bool last_stage);
void dirty_bitmap_mig_init(void);
bool should_send_vmdesc(void);
/* migration/block-active.c */
void migration_block_active_setup(bool active);
#endif
+17
View File
@@ -2026,6 +2026,7 @@ static void nbd_export_delete(BlockExport *blk_exp)
const BlockExportDriver blk_exp_nbd = {
.type = BLOCK_EXPORT_TYPE_NBD,
.instance_size = sizeof(NBDExport),
.supports_inactive = true,
.create = nbd_export_create,
.delete = nbd_export_delete,
.request_shutdown = nbd_export_request_shutdown,
@@ -2920,6 +2921,22 @@ static coroutine_fn int nbd_handle_request(NBDClient *client,
NBDExport *exp = client->exp;
char *msg;
size_t i;
bool inactive;
WITH_GRAPH_RDLOCK_GUARD() {
inactive = bdrv_is_inactive(blk_bs(exp->common.blk));
if (inactive) {
switch (request->type) {
case NBD_CMD_READ:
/* These commands are allowed on inactive nodes */
break;
default:
/* Return an error for the rest */
return nbd_send_generic_reply(client, request, -EPERM,
"export is inactive", errp);
}
}
}
switch (request->type) {
case NBD_CMD_CACHE:
+43 -1
View File
@@ -486,6 +486,10 @@
# @backing_file_depth: number of files in the backing file chain
# (since: 1.2)
#
# @active: true if the backend is active; typical cases for inactive backends
# are on the migration source instance after migration completes and on the
# destination before it completes. (since: 10.0)
#
# @encrypted: true if the backing device is encrypted
#
# @detect_zeroes: detect and optimize zero writes (Since 2.1)
@@ -556,7 +560,7 @@
{ 'struct': 'BlockDeviceInfo',
'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str',
'*backing_file': 'str', 'backing_file_depth': 'int',
'encrypted': 'bool',
'active': 'bool', 'encrypted': 'bool',
'detect_zeroes': 'BlockdevDetectZeroesOptions',
'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int',
'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int',
@@ -4679,6 +4683,11 @@
#
# @cache: cache-related options
#
# @active: whether the block node should be activated (default: true).
# Having inactive block nodes is useful primarily for migration because it
# allows opening an image on the destination while the source is still
# holding locks for it. (Since 10.0)
#
# @read-only: whether the block device should be read-only (default:
# false). Note that some block drivers support only read-only
# access, either generally or in certain configurations. In this
@@ -4705,6 +4714,7 @@
'*node-name': 'str',
'*discard': 'BlockdevDiscardOptions',
'*cache': 'BlockdevCacheOptions',
'*active': 'bool',
'*read-only': 'bool',
'*auto-read-only': 'bool',
'*force-share': 'bool',
@@ -4935,6 +4945,38 @@
{ 'command': 'blockdev-del', 'data': { 'node-name': 'str' },
'allow-preconfig': true }
##
# @blockdev-set-active:
#
# Activate or inactivate a block device. Use this to manage the handover of
# block devices on migration with qemu-storage-daemon.
#
# Activating a node automatically activates all of its child nodes first.
# Inactivating a node automatically inactivates any of its child nodes that are
# not in use by a still active node.
#
# @node-name: Name of the graph node to activate or inactivate. By default, all
# nodes are affected by the operation.
#
# @active: true if the nodes should be active when the command returns success,
# false if they should be inactive.
#
# Since: 10.0
#
# .. qmp-example::
#
# -> { "execute": "blockdev-set-active",
# "arguments": {
# "node-name": "node0",
# "active": false
# }
# }
# <- { "return": {} }
##
{ 'command': 'blockdev-set-active',
'data': { '*node-name': 'str', 'active': 'bool' },
'allow-preconfig': true }
##
# @BlockdevCreateOptionsFile:
#

Some files were not shown because too many files have changed in this diff Show More