vmspawn-qmp: add vmspawn_qmp_remove_block_device
Hot-remove counterpart to vmspawn_qmp_add_block_device. Looks the
drive up in the bridge's block_devices registry by caller-supplied
id and dispatches device_del using the internal qmp_device_id; the
varlink link gets the immediate ack/error reply once QEMU completes
the request.
Concurrency: a second remove for the same id while the first is in
flight (between device_del dispatch and DEVICE_DELETED) would
otherwise reach QEMU and earn a confusing 'already in the process of
unplug' reply. Track the in-flight state with a new
BLOCK_DEVICE_REMOVE_PENDING bit on the existing rollback_mask, and
short-circuit duplicate calls with -EBUSY. The bit is cleared on
device_del failure (the drive is still attached, so retries make
sense) and naturally vanishes on success when the registry entry is
dropped.
DEVICE_DELETED handling: the actual blockdev-del + registry removal
+ pcie-port release is deferred to vmspawn_qmp_dispatch_device_deleted,
which fires from on_qmp_event in vmspawn-varlink.c when the guest
acks the eject. Hooking it from the existing QMP event dispatcher
keeps the cleanup local to vmspawn-qmp.{c,h}.
The function has no varlink callers in this PR — the
io.systemd.VirtualMachineInstance method handler that forwards into
it lands with the rest of the hotplug PR.
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
@@ -991,6 +991,89 @@ static int qmp_setup_regular_drive(VmspawnQmpBridge *bridge, DriveInfo *drive) {
|
||||
return vmspawn_qmp_add_block_device(bridge, drive);
|
||||
}
|
||||
|
||||
/* device_del completion is just QEMU acking the request; teardown happens
|
||||
* in vmspawn_qmp_dispatch_device_deleted() once the guest acks the eject. */
|
||||
static int on_remove_device_del_complete(
|
||||
QmpClient *client,
|
||||
sd_json_variant *result,
|
||||
const char *error_desc,
|
||||
int error,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(drive_info_unrefp) DriveInfo *drive = ASSERT_PTR(userdata);
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link);
|
||||
|
||||
assert(client);
|
||||
assert(link);
|
||||
|
||||
if (error < 0) {
|
||||
/* device_del rejected: clear the pending bit so the caller can retry. */
|
||||
drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING;
|
||||
|
||||
return reply_qmp_error(link, error_desc, error);
|
||||
}
|
||||
|
||||
return sd_varlink_reply(link, NULL);
|
||||
}
|
||||
|
||||
int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id) {
|
||||
int r;
|
||||
|
||||
assert(bridge);
|
||||
assert(link);
|
||||
assert(id);
|
||||
|
||||
DriveInfo *drive = hashmap_get(bridge->block_devices, id);
|
||||
if (!drive)
|
||||
return reply_qmp_error(link, "Unknown block device id", -ENOENT);
|
||||
if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED))
|
||||
return reply_qmp_error(link, "Block device add pending", -EBUSY);
|
||||
if (FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_REMOVE_PENDING))
|
||||
return reply_qmp_error(link, "Block device removal pending", -EBUSY);
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL;
|
||||
r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("id", drive->qmp_device_id));
|
||||
if (r < 0)
|
||||
return sd_varlink_error_errno(link, r);
|
||||
|
||||
assert(!drive->link);
|
||||
drive->link = sd_varlink_ref(link);
|
||||
drive->state |= BLOCK_DEVICE_STATE_REMOVE_PENDING;
|
||||
|
||||
r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_del", QMP_CLIENT_ARGS(args),
|
||||
on_remove_device_del_complete, drive_info_ref(drive));
|
||||
if (r < 0) {
|
||||
drive->link = sd_varlink_unref(drive->link);
|
||||
drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING;
|
||||
drive_info_unref(drive);
|
||||
return sd_varlink_error_errno(link, r);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DEVICE_DELETED arrives once the guest has acked the eject; only then is it
|
||||
* safe to drop the blockdev node and release the registry slot (and PCIe port). */
|
||||
int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data) {
|
||||
assert(bridge);
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
const char *qmp_device_id = sd_json_variant_string(sd_json_variant_by_key(data, "device"));
|
||||
if (!qmp_device_id)
|
||||
return 0;
|
||||
|
||||
DriveInfo *drive = hashmap_get(bridge->block_devices_by_qmp_id, qmp_device_id);
|
||||
if (!drive)
|
||||
return 0;
|
||||
|
||||
vmspawn_qmp_block_device_teardown(bridge->qmp, drive->qmp_node_name, drive->state);
|
||||
|
||||
assert_se(bridge_unregister_drive(bridge, drive) == drive);
|
||||
drive_info_unref(drive);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *netdev_args = NULL, *device_args = NULL;
|
||||
bool tap_by_fd;
|
||||
|
||||
@@ -77,6 +77,7 @@ typedef enum QmpDriveFlags {
|
||||
typedef enum BlockDeviceStateFlags {
|
||||
BLOCK_DEVICE_STATE_BLOCKDEV_ADDED = 1u << 0,
|
||||
BLOCK_DEVICE_STATE_ADD_FAILED = 1u << 1, /* first error fired; suppress cascades */
|
||||
BLOCK_DEVICE_STATE_REMOVE_PENDING = 1u << 2, /* device_del in flight; reject concurrent removes */
|
||||
} BlockDeviceStateFlags;
|
||||
|
||||
/* Ref-counted; each of the four add-stage QMP slots holds one ref.
|
||||
@@ -176,3 +177,5 @@ int vmspawn_qmp_setup_drives(VmspawnQmpBridge *bridge, DriveInfos *drives);
|
||||
int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network);
|
||||
int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs);
|
||||
int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock);
|
||||
int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id);
|
||||
int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "varlink-io.systemd.QemuMachineInstance.h"
|
||||
#include "varlink-io.systemd.VirtualMachineInstance.h"
|
||||
#include "varlink-util.h"
|
||||
#include "vmspawn-qmp.h"
|
||||
#include "vmspawn-varlink.h"
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_FULL(
|
||||
@@ -315,6 +316,10 @@ static int on_qmp_event(
|
||||
if (streq(event, "JOB_STATUS_CHANGE"))
|
||||
return dispatch_pending_job(ctx->bridge, data);
|
||||
|
||||
/* Notification still fans out below. */
|
||||
if (streq(event, "DEVICE_DELETED"))
|
||||
(void) vmspawn_qmp_dispatch_device_deleted(ctx->bridge, data);
|
||||
|
||||
return notify_event_subscribers(ctx, event, data);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user