Akihiko Odaki <[email protected]> writes:

> On 2026/09/22 23:28, Fabiano Rosas wrote:
>> Akihiko Odaki <[email protected]> writes:
>> 
>>> xen-save-devices-state saves device VMState outside QEMU's normal
>>> migration-mode machinery, so it currently ignores migration blockers.
>>> Add a separate blocker class for this command.
>>>
>>> Blockers registered with migrate_add_blocker() or
>>> migrate_add_blocker_internal() now include this class. Existing
>>> mode-specific blocker masks remain unchanged, except that vhost-scsi's
>>> default blocker now covers normal migration and Xen device-state
>>> saving. Its CPR exemption assumes that the replacement QEMU runs on
>>> the same host and not concurrently with the old QEMU; Xen save and
>>> restore do not guarantee those conditions. Users can set
>>> migratable=true when shared storage or orchestrator-migrated target
>>> state makes migration safe.
>>>
>> 
>> So one thing is rejecting the xen-save-devices-state command if there
>> are blockers. This could very well just use the normal mode (default),
>> no? Or all modes, like qmp_dump_guest_memory() does?
>
> In general, blockers registered via migrate_add_blocker_normal() are 
> specific to standard QEMU live migration and do not apply to Xen. For 
> example, block formats like qcow or vhd register normal migration 
> blockers, but Xen explicitly supports migration with them [1]. 
> Unconditionally applying normal-mode blockers to Xen would cause 
> regressions. vhost-scsi is the exception here and is handled specifically.
>
> Checking every mode would lead to the same regression. Note that 
> qmp_dump_guest_memory() registers an all-mode blocker during execution; 
> it does not check all existing blocker lists when invoked.
>
> [1] https://xenbits.xen.org/docs/4.22-testing/SUPPORT.html#blkback
>
>> 
>> Another thing (complementary, could be in the same patch) is rejecting
>> the command line if there are blocked devices, including any ones used
>> along with Xen acceleration. Again, why does this need a new blocker
>> category? I'm not seeing it.
> You can use -only-migratable.
>
>> 
>>> Fixes: a7ae8355b446 ("Introduce "xen-save-devices-state"")
>>> Signed-off-by: Akihiko Odaki <[email protected]>
>>> ---
>>>   include/migration/blocker.h | 10 ++++++-
>>>   include/qemu/accel.h        |  2 +-
>>>   migration/migration.h       |  2 ++
>>>   accel/accel-system.c        | 25 +++++++++++++-----
>>>   hw/scsi/vhost-scsi.c        |  5 +++-
>>>   migration/migration.c       | 63 
>>> ++++++++++++++++++++++++++++++++++++++-------
>>>   migration/savevm.c          |  4 +++
>>>   system/vl.c                 |  9 ++++---
>>>   8 files changed, 99 insertions(+), 21 deletions(-)
>>>
>>> diff --git a/include/migration/blocker.h b/include/migration/blocker.h
>>> index 80b75ad5cbdb..0fbaaac83ce8 100644
>>> --- a/include/migration/blocker.h
>>> +++ b/include/migration/blocker.h
>>> @@ -16,6 +16,14 @@
>>>   
>>>   #include "qapi/qapi-types-migration.h"
>>>   
>>> +/**
>>> + * define MIG_XEN - identifier for Xen migration
>> 
>> Hm.
>> 
>>> + *
>>> + * Xen migration is distinct from those expressed with &typedef MigMode.
>>> + * Use this for migration blockers that affect Xen.
>>> + */
>>> +#define MIG_XEN MIG_MODE__MAX
>> 
>> This could be fine, Xen migration is pre-existing and 'mode' is part of
>> QAPI, we don't want to create a new mode for Xen.
>> 
>>> +
>>>   /**
>>>    * @migrate_add_blocker - prevent all modes of migration from proceeding
>>>    *
>>> @@ -80,7 +88,7 @@ int migrate_add_blocker_normal(Error **reasonp, Error 
>>> **errp);
>>>    *
>>>    * @reasonp - address of an error to be returned whenever migration is 
>>> attempted
>>>    *
>>> - * @modes - the migration modes to be blocked, a bit set of MigMode
>>> + * @modes - the migration modes to be blocked, a bit set of MigMode and 
>>> %MIG_XEN
>> 
>> But it's not a mode, so we shouldn't re-use @modes. We just want a place
>> to store the Xen blockers, let's not introduce the confusion that this
>> could be a mode somehow.
>> 
>> We could just add migrate_add_blocker_xen and call add_blockers directly
>> since you're changing that function to use the array size instead of
>> MIG_MODE__MAX.
>
> I would prefer to keep a single function for adding blockers across all 
> migration types so that the full scope of a blocker can be seen at a 
> single call site (see the vhost-scsi changes for an example).
>

Fair.

> Practically speaking, I think the risk of reusing @modes is minimal 
> because there is no alternative selective-blocking API to misuse. It 
> would indeed be cleaner to have a parameter name that encompasses both 
> MigMode and MIG_XEN, but I haven't come up with a better name yet; 
> suggestions are welcome.
>

Keep the @mode, but let's do away with any mentions of MigMode. This is
a "blocker mode", not "migration mode". wink wink

>> 
>>>    *
>>>    * @errp - [out] The reason (if any) we cannot block migration right now.
>>>    *
>>> diff --git a/include/qemu/accel.h b/include/qemu/accel.h
>>> index 6f65f1cb89a0..b6b456a82fc3 100644
>>> --- a/include/qemu/accel.h
>>> +++ b/include/qemu/accel.h
>>> @@ -47,7 +47,7 @@ const char *current_accel_name(void);
>>>   
>>>   void accel_init_interfaces(AccelClass *ac);
>>>   
>>> -int accel_init_machine(AccelState *accel, MachineState *ms);
>>> +int accel_init_machine(AccelState *accel, MachineState *ms, Error **errp);
>>>   
>>>   /* Called just before os_setup_post (ie just before drop OS privs) */
>>>   void accel_setup_post(MachineState *ms);
>>> diff --git a/migration/migration.h b/migration/migration.h
>>> index 683bc7bdd521..f53fdb2df107 100644
>>> --- a/migration/migration.h
>>> +++ b/migration/migration.h
>>> @@ -568,7 +568,9 @@ void migration_start_incoming(void);
>>>   int migration_call_notifiers(MigrationEventType type, Error **errp);
>>>   
>>>   int migrate_init(MigrationState *s, Error **errp);
>>> +bool enforce_only_migratable(Error **errp);
>>>   bool migration_is_blocked(Error **errp);
>>> +bool xen_migration_is_blocked(Error **errp);
>>>   /* True if outgoing migration has entered postcopy phase */
>>>   bool migration_in_postcopy(void);
>>>   bool migration_postcopy_is_alive(MigrationStatus state);
>>> diff --git a/accel/accel-system.c b/accel/accel-system.c
>>> index 1325b23864c8..75a691cb3cfe 100644
>>> --- a/accel/accel-system.c
>>> +++ b/accel/accel-system.c
>>> @@ -26,7 +26,9 @@
>>>   #include "qemu/osdep.h"
>>>   #include "qemu/accel.h"
>>>   #include "qom/compat-properties.h"
>>> +#include "qapi/error.h"
>>>   #include "qapi/qapi-commands-accelerator.h"
>>> +#include "migration/migration.h"
>>>   #include "monitor/monitor.h"
>>>   #include "monitor/hmp.h"
>>>   #include "hw/core/boards.h"
>>> @@ -37,20 +39,31 @@
>>>   #include "qemu/error-report.h"
>>>   #include "accel-internal.h"
>>>   
>>> -int accel_init_machine(AccelState *accel, MachineState *ms)
>>> +int accel_init_machine(AccelState *accel, MachineState *ms, Error **errp)
>>>   {
>>>       AccelClass *acc = ACCEL_GET_CLASS(accel);
>>>       int ret;
>>>       ms->accelerator = accel;
>>>       *(acc->allowed) = true;
>>> +
>>> +    if (!enforce_only_migratable(errp)) {
>>> +        ret = -EACCES;
>>> +        goto fail;
>>> +    }
>> 
>> Here I think you want to fail the initialization if the accelerator is
>> Xen, -only-migratable is set and there are blockers. At this point is
>> not clear whether these blockers really need a xen-specific category.
>
> The category reflects the specific requirements of the blocker itself. 
> Looking at vhost-scsi (which registers a Xen-affecting blocker) 
> clarifies why this distinction is necessary; this code merely enforces 
> registered blockers.
>
>> 
>> Isn't this too soon anyway? What blockers will be registered at this
>> point and if there are blockers on the list, why the
>> is_only_migratable() check at migrate_add_blocker_modes() hasn't already
>> rejected the cmdline?
>
> This check is placed here because qemu_create_early_backends() runs 
> before configure_accelerators(). Command-line backends (such as qcow or 
> vmdk) can register blockers before xen_enabled() accurately reflects the 
> selected accelerator.
>

Ok, so we want to validate that the blockers registered so far don't
affect the current accel. This information is important to have in a
comment somewhere.

Now, in the bad case we're going to fail the startup anyway, so wouldn't
it be better to avoid all the error handling and just make this a
generic check after configure_accelerators?

void qemu_init(int argc, char **argv)
{
    configure_accelerators(argv[0]);
    phase_advance(PHASE_ACCEL_CREATED);

    enforce_only_migratable();
    ...

>> 
>>> +
>>>       ret = acc->init_machine(accel, ms);
>>>       if (ret < 0) {
>>> -        ms->accelerator = NULL;
>>> -        *(acc->allowed) = false;
>>> -        object_unref(OBJECT(accel));
>>> -    } else {
>>> -        object_set_accelerator_compat_props(acc->compat_props);
>>> +        error_setg(errp, "%s", strerror(-ret));
>>> +        goto fail;
>>>       }
>>> +
>>> +    object_set_accelerator_compat_props(acc->compat_props);
>>> +    return ret;
>>> +
>>> +fail:
>>> +    ms->accelerator = NULL;
>>> +    *(acc->allowed) = false;
>>> +    object_unref(OBJECT(accel));
>>>       return ret;
>>>   }
>>>   
>>> diff --git a/hw/scsi/vhost-scsi.c b/hw/scsi/vhost-scsi.c
>>> index 657403cad011..f9847b5e7096 100644
>>> --- a/hw/scsi/vhost-scsi.c
>>> +++ b/hw/scsi/vhost-scsi.c
>>> @@ -262,12 +262,15 @@ static void vhost_scsi_realize(DeviceState *dev, 
>>> Error **errp)
>>>       }
>>>   
>>>       if (!vsc->migratable) {
>>> +        unsigned int modes = BIT(MIG_MODE_NORMAL) | BIT(MIG_XEN);
>>> +
>>>           error_setg(&vsc->migration_blocker,
>>>                   "vhost-scsi does not support migration in all cases. "
>>>                   "When external environment supports it (Orchestrator 
>>> migrates "
>>>                   "target SCSI device state or use shared storage over 
>>> network), "
>>>                   "set 'migratable' property to true to enable migration.");
>>> -        if (migrate_add_blocker_normal(&vsc->migration_blocker, errp) < 0) 
>>> {
>>> +        if (migrate_add_blocker_modes(&vsc->migration_blocker,
>>> +                                      modes, errp) < 0) {
>>>               goto free_virtio;
>>>           }
>>>       }
>>> diff --git a/migration/migration.c b/migration/migration.c
>>> index d0b864a76066..c177dc52a1c1 100644
>>> --- a/migration/migration.c
>>> +++ b/migration/migration.c
>>> @@ -23,6 +23,7 @@
>>>   #include "system/runstate.h"
>>>   #include "system/system.h"
>>>   #include "system/cpu-throttle.h"
>>> +#include "system/xen.h"
>>>   #include "ram.h"
>>>   #include "migration/cpr.h"
>>>   #include "migration/global_state.h"
>>> @@ -94,7 +95,8 @@ enum mig_rp_message_type {
>>>   static MigrationState *current_migration;
>>>   static MigrationIncomingState *current_incoming;
>>>   
>>> -static GSList *migration_blockers[MIG_MODE__MAX];
>>> +static GSList *migration_blockers[MIG_MODE__MAX + 1];
>>> +static bool enforcing_only_migratable;

I think I missed the need for this variable. Isn't only_migratable
enough?

>>>   
>>>   static bool migration_object_check(MigrationState *ms, Error **errp);
>>>   static bool migration_switchover_start(MigrationState *s, Error **errp);
>>> @@ -1760,15 +1762,22 @@ static bool is_busy(Error **reasonp, Error **errp)
>>>       return false;
>>>   }
>>>   
>>> +static void propagate_disallowed_blocker(Error **errp, Error **reasonp)
>>> +{
>>> +    error_propagate_prepend(errp, *reasonp,
>>> +                            "disallowing migration blocker "
>>> +                            "(--only-migratable) for: ");
>>> +    *reasonp = NULL;
>>> +}
>>> +
>>>   static bool is_only_migratable(Error **reasonp, unsigned modes, Error 
>>> **errp)
>>>   {
>>> +    unsigned mode = xen_enabled() ? MIG_XEN : MIG_MODE_NORMAL;
>>> +

Hmm, the original code is actually special casing CPR isn't it? I'd
prefer to be explicit instead:

    unsigned int ignore_only_migratable = (BIT(MIG_MODE_CPR_TRANSFER) |
                                           BIT(MIG_MODE_CPR_EXEC) |
                                           BIT(MIG_MODE_CPR_REBOOT));
    unsigned int cur_mode = blocker_mode() & ~ignore_only_migratable;

Where blocker_mode is:

/*
 * Migration blockers are scoped by migration mode. Nonetheless it's
 * still possible for an accelerator to have particularities that
 * require blocking migration, independently of mode. Use this helper
 * when indexing into migration_blockers to ensure the accelerator is
 * taken into account.
 */
static unsigned int *blocker_mode(void)
{
    if (phase_check(PHASE_ACCEL_CREATED)) {
        if (xen_enabled()) {
            return MIG_BLOCK_XEN;
        }
    }

    return migrate_mode();
}

>>>       ERRP_GUARD();
>>>   
>>> -    if (only_migratable && (modes & BIT(MIG_MODE_NORMAL))) {
>>> -        error_propagate_prepend(errp, *reasonp,
>>> -                                "disallowing migration blocker "
>>> -                                "(--only-migratable) for: ");
>>> -        *reasonp = NULL;
>>> +    if (enforcing_only_migratable && (modes & BIT(mode))) {
>>> +        propagate_disallowed_blocker(errp, reasonp);
>>>           return true;
>>>       }
>>>       return false;
>>> @@ -1776,7 +1785,7 @@ static bool is_only_migratable(Error **reasonp, 
>>> unsigned modes, Error **errp)
>>>   
>>>   static int add_blockers(Error **reasonp, unsigned modes, Error **errp)
>>>   {
>>> -    for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
>>> +    for (MigMode mode = 0; mode < ARRAY_SIZE(migration_blockers); mode++) {
>> 
>> Not a MigMode anymore.
>
> Good catch. I will fix the type in the next version.
>
>> 
>>>           if (modes & BIT(mode)) {
>>>               assert(g_slist_index(migration_blockers[mode],
>>>                                    *reasonp) == -1);
>>> @@ -1809,7 +1818,7 @@ int migrate_add_blocker_modes(Error **reasonp, 
>>> unsigned modes, Error **errp)
>>>   
>>>   int migrate_add_blocker_internal(Error **reasonp, Error **errp)
>>>   {
>>> -    unsigned modes = BIT(MIG_MODE__MAX) - 1;
>>> +    unsigned modes = BIT(ARRAY_SIZE(migration_blockers)) - 1;
>>>   
>>>       if (is_busy(reasonp, errp)) {
>>>           return -EBUSY;
>>> @@ -1820,7 +1829,7 @@ int migrate_add_blocker_internal(Error **reasonp, 
>>> Error **errp)
>>>   void migrate_del_blocker(Error **reasonp)
>>>   {
>>>       if (*reasonp) {
>>> -        for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
>>> +        for (MigMode mode = 0; mode < ARRAY_SIZE(migration_blockers); 
>>> mode++) {
>> 
>> Same here, just use int i.
>
> Will update the variable name and type accordingly.
>
>> 
>>>               migration_blockers[mode] = 
>>> g_slist_remove(migration_blockers[mode],
>>>                                                         *reasonp);
>>>           }
>>> @@ -1948,6 +1957,26 @@ void qmp_migrate_pause(Error **errp)
>>>                  "during postcopy-active or postcopy-recover state");
>>>   }
>>>   
>>> +bool enforce_only_migratable(Error **errp)
>>> +{
>>> +    unsigned mode = xen_enabled() ? MIG_XEN : MIG_MODE_NORMAL;
>>> +    GSList *blockers = migration_blockers[mode];
>> 
>> For Xen, what blockers are those? Is it the case that existing blockers
>> are simply not taking effect for Xen because the QMP command never
>> checks blockers? Or do you intend to add xen-specific blockers?
>
> The former.
>
> Existing migration blockers currently do not take effect for Xen, which 
> this patch fixes. However, applying all blockers from an existing 
> MigMode could cause a regression for the reasons stated above. 
> Therefore, this patch ensures that only blockers relevant to Xen are 
> applied.
>
> Specifically, it enforces the blockers added via migrate_add_blocker() 
> and the vhost-scsi blocker. The remaining blockers have not been proven 
> incompatible with Xen migration, so applying them unconditionally could 
> cause regressions.
>
> Regards,
> Akihiko Odaki
>
>> 
>>> +    Error *reason;
>>> +
>>> +    if (!only_migratable) {
>>> +        return true;
>>> +    }
>>> +
>>> +    if (blockers) {
>>> +        reason = error_copy(blockers->data);
>>> +        propagate_disallowed_blocker(errp, &reason);
>>> +        return false;
>>> +    }
>>> +
>>> +    enforcing_only_migratable = true;
>>> +    return true;
>>> +}

We could reduce the scope of migration_is_blocked so
enforce_only_migrate can call it instead of checking the blockers
itself:

// replaces migration_is_blocked
bool can_migrate(Error **errp)
{
    if (qemu_savevm_state_blocked(errp)) {
        return false;
    }

    if (migration_is_blocked(errp) {
        return false;
    }
    return true;
}

static bool migration_is_blocked(Error **errp)
{
    GSList *blockers;

    assert(phase_check(PHASE_ACCEL_CREATED));

    blockers = migration_blockers[blocker_mode()];
    if (blockers) {
        error_propagate(errp, error_copy(blockers->data));
        return true;
    }

    return false;
}

>>> +
>>>   bool migration_is_blocked(Error **errp)
>>>   {
>>>       GSList *blockers = migration_blockers[migrate_mode()];
>>> @@ -1964,6 +1993,22 @@ bool migration_is_blocked(Error **errp)
>>>       return false;
>>>   }
>>>   
>>> +bool xen_migration_is_blocked(Error **errp)
>>> +{
>>> +    GSList *blockers = migration_blockers[MIG_XEN];

Does this need to use MIG_XEN? If we use something like the
blocker_mode() I suggested, then this function can be replaced with
can_migrate() and we make all the points that start migration uniform.

What do you think? Let me know if I missed any details.

Thanks

>>> +
>>> +    if (qemu_savevm_state_blocked(errp)) {
>>> +        return true;
>>> +    }
>>> +
>>> +    if (blockers) {
>>> +        error_propagate(errp, error_copy(blockers->data));
>>> +        return true;
>>> +    }
>>> +
>>> +    return false;
>>> +}
>>> +
>>>   /* Returns true if continue to migrate, or false if error detected */
>>>   static bool migrate_prepare(MigrationState *s, bool resume, Error **errp)
>>>   {
>>> diff --git a/migration/savevm.c b/migration/savevm.c
>>> index 4b590ea672d2..4e5e1af1b202 100644
>>> --- a/migration/savevm.c
>>> +++ b/migration/savevm.c
>>> @@ -3418,6 +3418,10 @@ void qmp_xen_save_devices_state(const char 
>>> *filename, bool has_live, bool live,
>>>       int saved_vm_running;
>>>       int ret;
>>>   
>>> +    if (xen_migration_is_blocked(errp)) {
>>> +        return;
>>> +    }
>>> +
>>>       if (!has_live) {
>>>           /* live default to true so old version of Xen tool stack can have 
>>> a
>>>            * successful live migration */
>>> diff --git a/system/vl.c b/system/vl.c
>>> index 9bd7664b85c7..21fa810637c3 100644
>>> --- a/system/vl.c
>>> +++ b/system/vl.c
>>> @@ -2392,6 +2392,7 @@ static int accelerator_set_property(void *opaque,
>>>   
>>>   static int do_configure_accelerator(void *opaque, QemuOpts *opts, Error 
>>> **errp)
>>>   {
>>> +    Error *local_err = NULL;
>>>       bool *p_init_failed = opaque;
>>>       const char *acc = qemu_opt_get(opts, "accel");
>>>       AccelClass *ac = accel_find(acc);
>>> @@ -2418,10 +2419,12 @@ static int do_configure_accelerator(void *opaque, 
>>> QemuOpts *opts, Error **errp)
>>>                        accel,
>>>                        &error_fatal);
>>>   
>>> -    ret = accel_init_machine(accel, current_machine);
>>> +    ret = accel_init_machine(accel, current_machine, &local_err);
>>>       if (ret < 0) {
>>> -        if (!qtest_with_kvm || ret != -ENOENT) {
>>> -            error_report("failed to initialize %s: %s", acc, 
>>> strerror(-ret));
>>> +        if (qtest_with_kvm && ret == -ENOENT) {
>>> +            error_free(local_err);
>>> +        } else {
>>> +            error_reportf_err(local_err, "failed to initialize %s: ", acc);
>>>           }
>>>           goto bad;
>>>       }

Reply via email to