Commit 2d38eb7c74 for qemu.org
commit 2d38eb7c741eb8c176881c355e0084d0f508885d
Author: Stefan Hajnoczi <stefanha@redhat.com>
Date: Tue Oct 13 14:17:51 2026 -0400
scsi: clean up persistent reservation state after migration
Live migration of persistent reservations is designed to work without
coordination between the source and destination. The destination uses
the PREEMPT command to atomically take over the reservation or
unregister keys belonging to the source. This approach avoids race
conditions where other nodes in the cluster might acquire the
reservation and it minimizes communication during the downtime window.
While this works at the SCSI level, this approach is incomplete when
using multipathd. Multipathd tracks registered keys and automatically
registers them again when paths come up. It is unaware that the
destination has preempted the reservation and therefore it will still
attempt to register the key on the source host.
Use the new PR_HELPER_CLEANUP command to communicate with multipathd on
the source after migration completes successfully. The effect is similar
to REGISTER AND IGNORE EXISTING KEY, except it also discards
multipathd's state so that the source will no longer attempt to register
the key.
PR_HELPER_CLEANUP is a QEMU-internal command that is rejected by
scsi_req_new(), so note that this command is not exposed to guests
although direct blk_aio_ioctl(SG_IO) calls can make use of it.
Buglink: https://redhat.atlassian.net/browse/RHEL-274966
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Link: https://lore.kernel.org/r/20261013181752.1299456-8-stefanha@redhat.com
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index 3d88e57ede..6341089ddf 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -2773,13 +2773,32 @@ static void scsi_block_migration_failed(SCSIDiskState *s)
}
}
+/*
+ * Unregister keys on the source after migration since the destination has
+ * preempted and taken over. This cleanup is not necessary at the SCSI level
+ * but multipathd tracks registered keys and must be informed that this host no
+ * longer wishes to be registered.
+ */
+static void scsi_block_migration_done(SCSIDiskState *s)
+{
+ SCSIDevice *d = &s->qdev;
+ Error *local_err = NULL;
+
+ if (!scsi_generic_pr_state_cleanup(d, &local_err)) {
+ error_prepend(&local_err, "scsi-block migration done: ");
+ warn_report_err(local_err);
+ }
+}
+
static int scsi_block_migration_notifier(NotifierWithReturn *notifier,
MigrationEvent *e, Error **errp)
{
SCSIDiskState *s =
container_of(notifier, SCSIDiskState, migration_notifier);
- if (e->type == MIG_EVENT_FAILED) {
+ if (e->type == MIG_EVENT_DONE) {
+ scsi_block_migration_done(s);
+ } else if (e->type == MIG_EVENT_FAILED) {
scsi_block_migration_failed(s);
}
return 0;
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
index 153a527556..e41e973aa9 100644
--- a/hw/scsi/scsi-generic.c
+++ b/hw/scsi/scsi-generic.c
@@ -652,6 +652,35 @@ bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp)
return true;
}
+/* Clean up keys on source after migration */
+bool scsi_generic_pr_state_cleanup(SCSIDevice *s, Error **errp)
+{
+ SCSIPRState *pr_state = &s->pr_state;
+ uint8_t cmd[10] = {
+ PR_HELPER_CLEANUP, /* OPCODE */
+ };
+ int ret;
+
+ WITH_QEMU_LOCK_GUARD(&pr_state->mutex) {
+ if (pr_state->key == 0) {
+ return true; /* nothing to do */
+ }
+ }
+
+ ret = scsi_SG_IO(s->conf.blk, SG_DXFER_TO_DEV, cmd, sizeof(cmd), NULL, 0,
+ s->io_timeout, errp);
+ if (ret < 0) {
+ error_prepend(errp, "pr-helper CLEANUP failed: ");
+ return false;
+ }
+
+ WITH_QEMU_LOCK_GUARD(&pr_state->mutex) {
+ pr_state->key = 0;
+ pr_state->resv_type = 0;
+ }
+ return true;
+}
+
static void scsi_read_complete(void * opaque, int ret)
{
SCSIGenericReq *r = (SCSIGenericReq *)opaque;
diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h
index 3eab339b0d..a2f6f0be7f 100644
--- a/include/hw/scsi/scsi.h
+++ b/include/hw/scsi/scsi.h
@@ -256,6 +256,7 @@ SCSIDevice *scsi_device_get(SCSIBus *bus, int channel, int target, int lun);
/* scsi-generic.c. */
extern const SCSIReqOps scsi_generic_req_ops;
bool scsi_generic_pr_state_preempt(SCSIDevice *s, Error **errp);
+bool scsi_generic_pr_state_cleanup(SCSIDevice *s, Error **errp);
/* scsi-disk.c */
#define SCSI_DISK_QUIRK_MODE_PAGE_APPLE_VENDOR 0