[libvirt] [PATCH 00/10] Rollback migration when libvirtd restarts

This is the rest of the original 19 patch series updated with some bugfixes and rebased on current master, which is also available at https://gitorious.org/~jirka/libvirt/jirka-staging/commits/migration-recover... I didn't manage to run this through the libvirt-tck migration test but I did some additional testing with the following combinations of libvirt (current contains this series): source destination client ------------------------------------------- current current current current current 0.9.2-1.el6 current 0.9.2-2.fc15 current 0.9.2-2.fc15 current 0.9.2-1.el6 In all combinations I did - normal migration from src to dst - normal migration back - p2p migration from src to dst - p2p migration back To test failure recovery, I aborted the migration or libvirtd deamons at various places. All this was done with a single running domain without restarting it. Jiri Denemark (10): qemu: Implement migration job phases qemu: Migration job on destination daemon qemu: Migration job on source daemon qemu: Recover from interrupted migration qemu: Remove special case for virDomainGetBlockInfo qemu: Remove special case for virDomainBlockStats qemu: Remove special case for virDomainMigrateSetMaxSpeed qemu: Remove special case for virDomainMigrateSetMaxDowntime qemu: Remove special case for virDomainSuspend qemu: Remove special case for virDomainAbortJob include/libvirt/libvirt.h.in | 3 + src/libvirt.c | 27 ++- src/libvirt_internal.h | 6 + src/qemu/MIGRATION.txt | 55 ++++ src/qemu/qemu_domain.c | 18 +- src/qemu/qemu_domain.h | 31 +-- src/qemu/qemu_driver.c | 285 ++++++++++++--------- src/qemu/qemu_migration.c | 574 ++++++++++++++++++++++++------------------ src/qemu/qemu_migration.h | 40 +++- src/qemu/qemu_process.c | 121 +++++++++- 10 files changed, 750 insertions(+), 410 deletions(-) create mode 100644 src/qemu/MIGRATION.txt -- 1.7.6

This patch introduces several helper methods to deal with jobs and phases during migration in a simpler manner. --- src/qemu/MIGRATION.txt | 55 +++++++++++++++++++++++++++ src/qemu/qemu_domain.c | 5 ++ src/qemu/qemu_migration.c | 91 +++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_migration.h | 37 ++++++++++++++++++ 4 files changed, 188 insertions(+), 0 deletions(-) create mode 100644 src/qemu/MIGRATION.txt diff --git a/src/qemu/MIGRATION.txt b/src/qemu/MIGRATION.txt new file mode 100644 index 0000000..6c32998 --- /dev/null +++ b/src/qemu/MIGRATION.txt @@ -0,0 +1,55 @@ + QEMU Migration Locking Rules + ============================ + +Migration is a complicated beast which may span across several APIs on both +source and destination side and we need to keep the domain we are migrating in +a consistent state during the whole process. + +To avoid anyone from changing the domain in the middle of migration we need to +keep MIGRATION_OUT job active during migration from Begin to Confirm on the +source side and MIGRATION_IN job has to be active from Prepare to Finish on +the destination side. + +For this purpose we introduce several helper methods to deal with locking +primitives (described in THREADS.txt) in the right way: + +* qemuMigrationJobStart + +* qemuMigrationJobContinue + +* qemuMigrationJobStartPhase + +* qemuMigrationJobSetPhase + +* qemuMigrationJobFinish + +The sequence of calling qemuMigrationJob* helper methods is as follows: + +- The first API of a migration protocol (Prepare or Perform/Begin depending on + migration type and version) has to start migration job and keep it active: + + qemuMigrationJobStart(driver, vm, QEMU_JOB_MIGRATION_{IN,OUT}); + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_*); + ...do work... + qemuMigrationJobContinue(vm); + +- All consequent phases except for the last one have to keep the job active: + + if (!qemuMigrationJobIsActive(vm, QEMU_JOB_MIGRATION_{IN,OUT})) + return; + qemuMigrationJobStartPhase(driver, vm, QEMU_MIGRATION_PHASE_*); + ...do work... + qemuMigrationJobContinue(vm); + +- The last migration phase finally finishes the migration job: + + if (!qemuMigrationJobIsActive(vm, QEMU_JOB_MIGRATION_{IN,OUT})) + return; + qemuMigrationJobStartPhase(driver, vm, QEMU_MIGRATION_PHASE_*); + ...do work... + qemuMigrationJobFinish(driver, vm); + +While migration job is running (i.e., after qemuMigrationJobStart* but before +qemuMigrationJob{Continue,Finish}), migration phase can be advanced using + + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_*); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index f9755a4..fee562d 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -26,6 +26,7 @@ #include "qemu_domain.h" #include "qemu_command.h" #include "qemu_capabilities.h" +#include "qemu_migration.h" #include "memory.h" #include "logging.h" #include "virterror_internal.h" @@ -72,6 +73,8 @@ qemuDomainAsyncJobPhaseToString(enum qemuDomainAsyncJob job, switch (job) { case QEMU_ASYNC_JOB_MIGRATION_OUT: case QEMU_ASYNC_JOB_MIGRATION_IN: + return qemuMigrationJobPhaseTypeToString(phase); + case QEMU_ASYNC_JOB_SAVE: case QEMU_ASYNC_JOB_DUMP: case QEMU_ASYNC_JOB_NONE: @@ -92,6 +95,8 @@ qemuDomainAsyncJobPhaseFromString(enum qemuDomainAsyncJob job, switch (job) { case QEMU_ASYNC_JOB_MIGRATION_OUT: case QEMU_ASYNC_JOB_MIGRATION_IN: + return qemuMigrationJobPhaseTypeFromString(phase); + case QEMU_ASYNC_JOB_SAVE: case QEMU_ASYNC_JOB_DUMP: case QEMU_ASYNC_JOB_NONE: diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index dfa80e3..9659e8d 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -46,6 +46,19 @@ #define VIR_FROM_THIS VIR_FROM_QEMU +VIR_ENUM_IMPL(qemuMigrationJobPhase, QEMU_MIGRATION_PHASE_LAST, + "none", + "preform2", + "begin3", + "perform3", + "perform3_done", + "confirm3_cancelled", + "confirm3", + "prepare", + "finish2", + "finish3", +); + enum qemuMigrationCookieFlags { QEMU_MIGRATION_COOKIE_FLAG_GRAPHICS, QEMU_MIGRATION_COOKIE_FLAG_LOCKSTATE, @@ -2770,3 +2783,81 @@ cleanup: } return ret; } + +int +qemuMigrationJobStart(struct qemud_driver *driver, + virDomainObjPtr vm, + enum qemuDomainAsyncJob job) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm, job) < 0) + return -1; + + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) + qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE); + else + qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK); + + priv->job.info.type = VIR_DOMAIN_JOB_UNBOUNDED; + + return 0; +} + +void +qemuMigrationJobSetPhase(struct qemud_driver *driver, + virDomainObjPtr vm, + enum qemuMigrationJobPhase phase) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (phase < priv->job.phase) { + VIR_ERROR(_("migration protocol going backwards %s => %s"), + qemuMigrationJobPhaseTypeToString(priv->job.phase), + qemuMigrationJobPhaseTypeToString(phase)); + return; + } + + qemuDomainObjSetJobPhase(driver, vm, phase); +} + +void +qemuMigrationJobStartPhase(struct qemud_driver *driver, + virDomainObjPtr vm, + enum qemuMigrationJobPhase phase) +{ + virDomainObjRef(vm); + qemuMigrationJobSetPhase(driver, vm, phase); +} + +int +qemuMigrationJobContinue(virDomainObjPtr vm) +{ + return virDomainObjUnref(vm); +} + +bool +qemuMigrationJobIsActive(virDomainObjPtr vm, + enum qemuDomainAsyncJob job) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (priv->job.asyncJob != job) { + const char *msg; + + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) + msg = _("domain '%s' is not processing incoming migration"); + else + msg = _("domain '%s' is not being migrated"); + + qemuReportError(VIR_ERR_OPERATION_INVALID, msg, vm->def->name); + return false; + } + return true; +} + +int +qemuMigrationJobFinish(struct qemud_driver *driver, virDomainObjPtr vm) +{ + return qemuDomainObjEndAsyncJob(driver, vm); +} diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index 3a9b94e..005e415 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -23,6 +23,7 @@ # define __QEMU_MIGRATION_H__ # include "qemu_conf.h" +# include "qemu_domain.h" /* All supported qemu migration flags. */ # define QEMU_MIGRATION_FLAGS \ @@ -35,6 +36,42 @@ VIR_MIGRATE_NON_SHARED_DISK | \ VIR_MIGRATE_NON_SHARED_INC) +enum qemuMigrationJobPhase { + QEMU_MIGRATION_PHASE_NONE = 0, + QEMU_MIGRATION_PHASE_PERFORM2, + QEMU_MIGRATION_PHASE_BEGIN3, + QEMU_MIGRATION_PHASE_PERFORM3, + QEMU_MIGRATION_PHASE_PERFORM3_DONE, + QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED, + QEMU_MIGRATION_PHASE_CONFIRM3, + QEMU_MIGRATION_PHASE_PREPARE, + QEMU_MIGRATION_PHASE_FINISH2, + QEMU_MIGRATION_PHASE_FINISH3, + + QEMU_MIGRATION_PHASE_LAST +}; +VIR_ENUM_DECL(qemuMigrationJobPhase) + +int qemuMigrationJobStart(struct qemud_driver *driver, + virDomainObjPtr vm, + enum qemuDomainAsyncJob job) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; +void qemuMigrationJobSetPhase(struct qemud_driver *driver, + virDomainObjPtr vm, + enum qemuMigrationJobPhase phase) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +void qemuMigrationJobStartPhase(struct qemud_driver *driver, + virDomainObjPtr vm, + enum qemuMigrationJobPhase phase) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int qemuMigrationJobContinue(virDomainObjPtr obj) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_RETURN_CHECK; +bool qemuMigrationJobIsActive(virDomainObjPtr vm, + enum qemuDomainAsyncJob job) + ATTRIBUTE_NONNULL(1); +int qemuMigrationJobFinish(struct qemud_driver *driver, virDomainObjPtr obj) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; + bool qemuMigrationIsAllowed(virDomainDefPtr def) ATTRIBUTE_NONNULL(1); int qemuMigrationSetOffline(struct qemud_driver *driver, -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
This patch introduces several helper methods to deal with jobs and phases during migration in a simpler manner. --- src/qemu/MIGRATION.txt | 55 +++++++++++++++++++++++++++ src/qemu/qemu_domain.c | 5 ++ src/qemu/qemu_migration.c | 91 +++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_migration.h | 37 ++++++++++++++++++ 4 files changed, 188 insertions(+), 0 deletions(-) create mode 100644 src/qemu/MIGRATION.txt
diff --git a/src/qemu/MIGRATION.txt b/src/qemu/MIGRATION.txt new file mode 100644 index 0000000..6c32998 --- /dev/null +++ b/src/qemu/MIGRATION.txt @@ -0,0 +1,55 @@ + QEMU Migration Locking Rules + ============================
Cool file, but maybe it could also use an overview of the three migration protocols as a separate section, so that you can refer to the migration phases in the same document.
+++ b/src/qemu/qemu_migration.c @@ -46,6 +46,19 @@
#define VIR_FROM_THIS VIR_FROM_QEMU
+VIR_ENUM_IMPL(qemuMigrationJobPhase, QEMU_MIGRATION_PHASE_LAST, + "none", + "preform2",
s/preform/perform/ ACK with this squashed in: diff --git i/src/qemu/MIGRATION.txt w/src/qemu/MIGRATION.txt index 6c32998..a26a0f3 100644 --- i/src/qemu/MIGRATION.txt +++ w/src/qemu/MIGRATION.txt @@ -1,3 +1,48 @@ + QEMU Migration Phases + ===================== + +Qemu supports only migration protocols 2 and 3 (1 was lacking too many +steps). Repeating the protocol sequences from libvirt.c: + +Sequence v2: + + Src: DumpXML + - Generate XML to pass to dst + + Dst: Prepare + - Get ready to accept incoming VM + - Generate optional cookie to pass to src + + Src: Perform + - Start migration and wait for send completion + - Kill off VM if successful, resume if failed + + Dst: Finish + - Wait for recv completion and check status + - Kill off VM if unsuccessful + +Sequence v3: + + Src: Begin + - Generate XML to pass to dst + - Generate optional cookie to pass to dst + + Dst: Prepare + - Get ready to accept incoming VM + - Generate optional cookie to pass to src + + Src: Perform + - Start migration and wait for send completion + - Generate optional cookie to pass to dst + + Dst: Finish + - Wait for recv completion and check status + - Kill off VM if failed, resume if success + - Generate optional cookie to pass to src + + Src: Confirm + - Kill off VM if success, resume if failed + QEMU Migration Locking Rules ============================ diff --git i/src/qemu/qemu_migration.c w/src/qemu/qemu_migration.c index 6ca9aa5..fba88c9 100644 --- i/src/qemu/qemu_migration.c +++ w/src/qemu/qemu_migration.c @@ -48,7 +48,7 @@ VIR_ENUM_IMPL(qemuMigrationJobPhase, QEMU_MIGRATION_PHASE_LAST, "none", - "preform2", + "perform2", "begin3", "perform3", "perform3_done", -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Make MIGRATION_IN use the new helper methods. --- src/qemu/qemu_domain.c | 2 +- src/qemu/qemu_domain.h | 1 - src/qemu/qemu_migration.c | 97 ++++++++++++++++++-------------------------- 3 files changed, 41 insertions(+), 59 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index fee562d..d2f03dd 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -645,7 +645,7 @@ void qemuDomainSetNamespaceHooks(virCapsPtr caps) caps->ns.href = qemuDomainDefNamespaceHref; } -void +static void qemuDomainObjSaveJob(struct qemud_driver *driver, virDomainObjPtr obj) { if (!virDomainObjIsActive(obj)) { diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 2ba6007..45fae55 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -187,7 +187,6 @@ int qemuDomainObjEndAsyncJob(struct qemud_driver *driver, void qemuDomainObjEndNestedJob(struct qemud_driver *driver, virDomainObjPtr obj); -void qemuDomainObjSaveJob(struct qemud_driver *driver, virDomainObjPtr obj); void qemuDomainObjSetJobPhase(struct qemud_driver *driver, virDomainObjPtr obj, int phase); diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 9659e8d..6e7117b 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1130,9 +1130,9 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, QEMU_MIGRATION_COOKIE_LOCKSTATE))) goto cleanup; - if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm, - QEMU_ASYNC_JOB_MIGRATION_IN) < 0) + if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_IN) < 0) goto cleanup; + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PREPARE); /* Domain starts inactive, even if the domain XML had an id field. */ vm->def->id = -1; @@ -1190,28 +1190,19 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_MIGRATED); - ret = 0; -endjob: - if (qemuDomainObjEndAsyncJob(driver, vm) == 0) { - vm = NULL; - } else if (!vm->persistent && !virDomainObjIsActive(vm)) { - virDomainRemoveInactive(&driver->domains, vm); + /* We keep the job active across API calls until the finish() call. + * This prevents any other APIs being invoked while incoming + * migration is taking place. + */ + if (qemuMigrationJobContinue(vm) == 0) { vm = NULL; + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("domain disappeared")); + goto cleanup; } - /* We set a fake job active which is held across - * API calls until the finish() call. This prevents - * any other APIs being invoked while incoming - * migration is taking place - */ - if (vm && - virDomainObjIsActive(vm)) { - priv->job.asyncJob = QEMU_ASYNC_JOB_MIGRATION_IN; - qemuDomainObjSaveJob(driver, vm); - priv->job.info.type = VIR_DOMAIN_JOB_UNBOUNDED; - priv->job.start = now; - } + ret = 0; cleanup: virDomainDefFree(def); @@ -1223,6 +1214,15 @@ cleanup: qemuDomainEventQueue(driver, event); qemuMigrationCookieFree(mig); return ret; + +endjob: + if (qemuMigrationJobFinish(driver, vm) == 0) { + vm = NULL; + } else if (!vm->persistent) { + virDomainRemoveInactive(&driver->domains, vm); + vm = NULL; + } + goto cleanup; } @@ -2397,27 +2397,23 @@ qemuMigrationFinish(struct qemud_driver *driver, virDomainPtr dom = NULL; virDomainEventPtr event = NULL; int newVM = 1; - qemuDomainObjPrivatePtr priv = NULL; qemuMigrationCookiePtr mig = NULL; + virErrorPtr orig_err = NULL; + VIR_DEBUG("driver=%p, dconn=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, retcode=%d", driver, dconn, vm, NULLSTR(cookiein), cookieinlen, cookieout, cookieoutlen, flags, retcode); - virErrorPtr orig_err = NULL; - priv = vm->privateData; - if (priv->job.asyncJob != QEMU_ASYNC_JOB_MIGRATION_IN) { - qemuReportError(VIR_ERR_NO_DOMAIN, - _("domain '%s' is not processing incoming migration"), vm->def->name); + if (!qemuMigrationJobIsActive(vm, QEMU_ASYNC_JOB_MIGRATION_IN)) goto cleanup; - } - qemuDomainObjDiscardAsyncJob(driver, vm); - if (!(mig = qemuMigrationEatCookie(driver, vm, cookiein, cookieinlen, 0))) - goto cleanup; + qemuMigrationJobStartPhase(driver, vm, + v3proto ? QEMU_MIGRATION_PHASE_FINISH3 + : QEMU_MIGRATION_PHASE_FINISH2); - if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) - goto cleanup; + if (!(mig = qemuMigrationEatCookie(driver, vm, cookiein, cookieinlen, 0))) + goto endjob; /* Did the migration go as planned? If yes, return the domain * object, but if no, clean up the empty qemu process. @@ -2448,21 +2444,14 @@ qemuMigrationFinish(struct qemud_driver *driver, */ /* - * In v3 protocol, the source VM is still available to - * restart during confirm() step, so we kill it off - * now. - * In v2 protocol, the source is dead, so we leave - * target in paused state, in case admin can fix - * things up + * However, in v3 protocol, the source VM is still available + * to restart during confirm() step, so we kill it off now. */ if (v3proto) { qemuProcessStop(driver, vm, 1, VIR_DOMAIN_SHUTOFF_FAILED); virDomainAuditStop(vm, "failed"); - if (newVM) { - if (qemuDomainObjEndJob(driver, vm) > 0) - virDomainRemoveInactive(&driver->domains, vm); - vm = NULL; - } + if (newVM) + vm->persistent = 0; } goto endjob; } @@ -2475,7 +2464,6 @@ qemuMigrationFinish(struct qemud_driver *driver, if (event) qemuDomainEventQueue(driver, event); event = NULL; - } if (!(flags & VIR_MIGRATE_PAUSED)) { @@ -2507,11 +2495,6 @@ qemuMigrationFinish(struct qemud_driver *driver, event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_FAILED); - if (!vm->persistent) { - if (qemuDomainObjEndJob(driver, vm) > 0) - virDomainRemoveInactive(&driver->domains, vm); - vm = NULL; - } } goto endjob; } @@ -2543,20 +2526,20 @@ qemuMigrationFinish(struct qemud_driver *driver, event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_FAILED); - if (!vm->persistent) { - if (qemuDomainObjEndJob(driver, vm) > 0) - virDomainRemoveInactive(&driver->domains, vm); - vm = NULL; - } } if (qemuMigrationBakeCookie(mig, driver, vm, cookieout, cookieoutlen, 0) < 0) VIR_WARN("Unable to encode migration cookie"); endjob: - if (vm && - qemuDomainObjEndJob(driver, vm) == 0) - vm = NULL; + if (vm) { + if (qemuMigrationJobFinish(driver, vm) == 0) { + vm = NULL; + } else if (!vm->persistent && !virDomainObjIsActive(vm)) { + virDomainRemoveInactive(&driver->domains, vm); + vm = NULL; + } + } cleanup: if (vm) -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Make MIGRATION_IN use the new helper methods. --- src/qemu/qemu_domain.c | 2 +- src/qemu/qemu_domain.h | 1 - src/qemu/qemu_migration.c | 97 ++++++++++++++++++-------------------------- 3 files changed, 41 insertions(+), 59 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Make MIGRATION_OUT use the new helper methods. This also introduces new protection to migration v3 process: the migration job is held from Begin to Confirm to avoid changes to a domain during migration (esp. between Begin and Perform phases). This change is automatically applied to p2p and tunneled migrations. For normal migration, this requires support from a client. In other words, if an old (pre 0.9.4) client starts normal migration of a domain, the domain will not be protected against changes between Begin and Perform steps. --- include/libvirt/libvirt.h.in | 3 + src/libvirt.c | 27 ++++- src/libvirt_internal.h | 6 + src/qemu/qemu_driver.c | 61 +++++++++- src/qemu/qemu_migration.c | 272 ++++++++++++++++++++++++++++++------------ src/qemu/qemu_migration.h | 3 +- 6 files changed, 285 insertions(+), 87 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 607b5bc..18cc521 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -674,6 +674,9 @@ typedef enum { VIR_MIGRATE_NON_SHARED_DISK = (1 << 6), /* migration with non-shared storage with full disk copy */ VIR_MIGRATE_NON_SHARED_INC = (1 << 7), /* migration with non-shared storage with incremental copy */ /* (same base image shared between source and destination) */ + VIR_MIGRATE_CHANGE_PROTECTION = (1 << 8), /* protect for changing domain configuration through the + * whole migration process; this will be used automatically + * if both parties support it */ } virDomainMigrateFlags; diff --git a/src/libvirt.c b/src/libvirt.c index 39e2041..446a47b 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -3763,7 +3763,9 @@ virDomainMigrateVersion3(virDomainPtr domain, int ret; virDomainInfo info; virErrorPtr orig_err = NULL; - int cancelled; + int cancelled = 1; + unsigned long protection = 0; + VIR_DOMAIN_DEBUG(domain, "dconn=%p xmlin=%s, flags=%lx, " "dname=%s, uri=%s, bandwidth=%lu", dconn, NULLSTR(xmlin), flags, @@ -3779,10 +3781,14 @@ virDomainMigrateVersion3(virDomainPtr domain, return NULL; } + if (VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn, + VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION)) + protection = VIR_MIGRATE_CHANGE_PROTECTION; + VIR_DEBUG("Begin3 %p", domain->conn); dom_xml = domain->conn->driver->domainMigrateBegin3 (domain, xmlin, &cookieout, &cookieoutlen, - flags, dname, bandwidth); + flags | protection, dname, bandwidth); if (!dom_xml) goto done; @@ -3800,14 +3806,22 @@ virDomainMigrateVersion3(virDomainPtr domain, (dconn, cookiein, cookieinlen, &cookieout, &cookieoutlen, uri, &uri_out, flags, dname, bandwidth, dom_xml); VIR_FREE (dom_xml); - if (ret == -1) - goto done; + if (ret == -1) { + if (protection) { + /* Begin already started a migration job so we need to cancel it by + * calling Confirm while making sure it doesn't overwrite the error + */ + orig_err = virSaveLastError(); + goto confirm; + } else { + goto done; + } + } if (uri == NULL && uri_out == NULL) { virLibConnError(VIR_ERR_INTERNAL_ERROR, _("domainMigratePrepare3 did not set uri")); virDispatchError(domain->conn); - cancelled = 1; goto finish; } if (uri_out) @@ -3828,7 +3842,7 @@ virDomainMigrateVersion3(virDomainPtr domain, ret = domain->conn->driver->domainMigratePerform3 (domain, NULL, cookiein, cookieinlen, &cookieout, &cookieoutlen, NULL, - uri, flags, dname, bandwidth); + uri, flags | protection, dname, bandwidth); /* Perform failed. Make sure Finish doesn't overwrite the error */ if (ret < 0) @@ -3873,6 +3887,7 @@ finish: if (!orig_err) orig_err = virSaveLastError(); +confirm: /* * If cancelled, then src VM will be restarted, else * it will be killed diff --git a/src/libvirt_internal.h b/src/libvirt_internal.h index 83c25fc..6e44341 100644 --- a/src/libvirt_internal.h +++ b/src/libvirt_internal.h @@ -73,6 +73,12 @@ enum { * domainMigrateConfirm3. */ VIR_DRV_FEATURE_MIGRATION_V3 = 6, + + /* + * Driver supports protecting the whole V3-style migration against changes + * to domain configuration, i.e., starting from Begin3 and not Perform3. + */ + VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION = 7, }; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8870e33..b378cb7 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -889,6 +889,7 @@ qemudSupportsFeature (virConnectPtr conn ATTRIBUTE_UNUSED, int feature) case VIR_DRV_FEATURE_MIGRATION_V2: case VIR_DRV_FEATURE_MIGRATION_V3: case VIR_DRV_FEATURE_MIGRATION_P2P: + case VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION: return 1; default: return 0; @@ -6869,12 +6870,56 @@ qemuDomainMigrateBegin3(virDomainPtr domain, goto cleanup; } - xml = qemuMigrationBegin(driver, vm, xmlin, - cookieout, cookieoutlen); + if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { + if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) + goto cleanup; + } else { + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + + if (!(xml = qemuMigrationBegin(driver, vm, xmlin, + cookieout, cookieoutlen))) + goto endjob; + + if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { + /* We keep the job active across API calls until the confirm() call. + * This prevents any other APIs being invoked while migration is taking + * place. + */ + if (qemuMigrationJobContinue(vm) == 0) { + vm = NULL; + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("domain disappeared")); + VIR_FREE(xml); + if (cookieout) + VIR_FREE(*cookieout); + } + } else { + goto endjob; + } cleanup: + if (vm) + virDomainObjUnlock(vm); qemuDriverUnlock(driver); return xml; + +endjob: + if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { + if (qemuMigrationJobFinish(driver, vm) == 0) + vm = NULL; + } else { + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; + } + goto cleanup; } static int @@ -7056,6 +7101,7 @@ qemuDomainMigrateConfirm3(virDomainPtr domain, struct qemud_driver *driver = domain->conn->privateData; virDomainObjPtr vm; int ret = -1; + enum qemuMigrationJobPhase phase; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); @@ -7069,14 +7115,21 @@ qemuDomainMigrateConfirm3(virDomainPtr domain, goto cleanup; } - if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) + if (!qemuMigrationJobIsActive(vm, QEMU_ASYNC_JOB_MIGRATION_OUT)) goto cleanup; + if (cancelled) + phase = QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED; + else + phase = QEMU_MIGRATION_PHASE_CONFIRM3; + + qemuMigrationJobStartPhase(driver, vm, phase); + ret = qemuMigrationConfirm(driver, domain->conn, vm, cookiein, cookieinlen, flags, cancelled); - if (qemuDomainObjEndJob(driver, vm) == 0) { + if (qemuMigrationJobFinish(driver, vm) == 0) { vm = NULL; } else if (!virDomainObjIsActive(vm) && (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE))) { diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 6e7117b..3eeb67f 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1009,6 +1009,7 @@ qemuDomainMigrateGraphicsRelocate(struct qemud_driver *driver, } +/* The caller is supposed to lock the vm and start a migration job. */ char *qemuMigrationBegin(struct qemud_driver *driver, virDomainObjPtr vm, const char *xmlin, @@ -1018,14 +1019,17 @@ char *qemuMigrationBegin(struct qemud_driver *driver, char *rv = NULL; qemuMigrationCookiePtr mig = NULL; virDomainDefPtr def = NULL; + qemuDomainObjPrivatePtr priv = vm->privateData; + VIR_DEBUG("driver=%p, vm=%p, xmlin=%s, cookieout=%p, cookieoutlen=%p", driver, vm, NULLSTR(xmlin), cookieout, cookieoutlen); - if (!virDomainObjIsActive(vm)) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is not running")); - goto cleanup; - } + /* Only set the phase if we are inside QEMU_ASYNC_JOB_MIGRATION_OUT. + * Otherwise we will start the async job later in the perform phase losing + * change protection. + */ + if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_BEGIN3); if (qemuProcessAutoDestroyActive(driver, vm)) { qemuReportError(VIR_ERR_OPERATION_INVALID, @@ -1063,7 +1067,6 @@ char *qemuMigrationBegin(struct qemud_driver *driver, } cleanup: - virDomainObjUnlock(vm); qemuMigrationCookieFree(mig); virDomainDefFree(def); return rv; @@ -1904,6 +1907,7 @@ static int doPeer2PeerMigrate2(struct qemud_driver *driver, * until the migration is complete. */ VIR_DEBUG("Perform %p", sconn); + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM2); if (flags & VIR_MIGRATE_TUNNELLED) ret = doTunnelMigrate(driver, vm, st, NULL, 0, NULL, NULL, @@ -2038,6 +2042,7 @@ static int doPeer2PeerMigrate3(struct qemud_driver *driver, * confirm migration completion. */ VIR_DEBUG("Perform3 %p uri=%s uri_out=%s", sconn, uri, uri_out); + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM3); VIR_FREE(cookiein); cookiein = cookieout; cookieinlen = cookieoutlen; @@ -2055,8 +2060,12 @@ static int doPeer2PeerMigrate3(struct qemud_driver *driver, flags, dname, resource); /* Perform failed. Make sure Finish doesn't overwrite the error */ - if (ret < 0) + if (ret < 0) { orig_err = virSaveLastError(); + } else { + qemuMigrationJobSetPhase(driver, vm, + QEMU_MIGRATION_PHASE_PERFORM3_DONE); + } /* If Perform returns < 0, then we need to cancel the VM * startup on the destination @@ -2213,35 +2222,32 @@ cleanup: } -int qemuMigrationPerform(struct qemud_driver *driver, - virConnectPtr conn, - virDomainObjPtr vm, - const char *xmlin, - const char *dconnuri, - const char *uri, - const char *cookiein, - int cookieinlen, - char **cookieout, - int *cookieoutlen, - unsigned long flags, - const char *dname, - unsigned long resource, - bool v3proto) +/* + * This implements perform part of the migration protocol when migration job + * does not need to be active across several APIs, i.e., peer2peer migration or + * perform phase of v2 non-peer2peer migration. + */ +static int +qemuMigrationPerformJob(struct qemud_driver *driver, + virConnectPtr conn, + virDomainObjPtr vm, + const char *xmlin, + const char *dconnuri, + const char *uri, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + unsigned long flags, + const char *dname, + unsigned long resource, + bool v3proto) { virDomainEventPtr event = NULL; int ret = -1; int resume = 0; - qemuDomainObjPrivatePtr priv = vm->privateData; - VIR_DEBUG("driver=%p, conn=%p, vm=%p, xmlin=%s, dconnuri=%s, " - "uri=%s, cookiein=%s, cookieinlen=%d, cookieout=%p, " - "cookieoutlen=%p, flags=%lx, dname=%s, resource=%lu, v3proto=%d", - driver, conn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), - NULLSTR(uri), NULLSTR(cookiein), cookieinlen, - cookieout, cookieoutlen, flags, NULLSTR(dname), - resource, v3proto); - if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm, - QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) + if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) goto cleanup; if (!virDomainObjIsActive(vm)) { @@ -2256,52 +2262,33 @@ int qemuMigrationPerform(struct qemud_driver *driver, goto endjob; } - memset(&priv->job.info, 0, sizeof(priv->job.info)); - priv->job.info.type = VIR_DOMAIN_JOB_UNBOUNDED; - resume = virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING; if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { - if (cookieinlen) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("received unexpected cookie with P2P migration")); - goto endjob; - } - - if (doPeer2PeerMigrate(driver, conn, vm, xmlin, - dconnuri, uri, flags, dname, - resource, &v3proto) < 0) - /* doPeer2PeerMigrate already set the error, so just get out */ - goto endjob; + ret = doPeer2PeerMigrate(driver, conn, vm, xmlin, + dconnuri, uri, flags, dname, + resource, &v3proto); } else { - if (dconnuri) { - qemuReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("Unexpected dconnuri parameter with non-peer2peer migration")); - goto endjob; - } - if (doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, - cookieout, cookieoutlen, - flags, dname, resource) < 0) - goto endjob; + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM2); + ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, + cookieout, cookieoutlen, + flags, dname, resource); } + if (ret < 0) + goto endjob; /* * In v3 protocol, the source VM is not killed off until the * confirm step. */ - if (v3proto) { - resume = 0; - } else { + if (!v3proto) { qemuProcessStop(driver, vm, 1, VIR_DOMAIN_SHUTOFF_MIGRATED); virDomainAuditStop(vm, "migrated"); - resume = 0; - event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_MIGRATED); } - - ret = 0; + resume = 0; endjob: if (resume && virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) { @@ -2320,16 +2307,95 @@ endjob: VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_MIGRATED); } - if (vm) { - if (qemuDomainObjEndAsyncJob(driver, vm) == 0) { - vm = NULL; - } else if (!virDomainObjIsActive(vm) && - (!vm->persistent || (flags & VIR_MIGRATE_UNDEFINE_SOURCE))) { - if (flags & VIR_MIGRATE_UNDEFINE_SOURCE) - virDomainDeleteConfig(driver->configDir, driver->autostartDir, vm); - virDomainRemoveInactive(&driver->domains, vm); - vm = NULL; + + if (qemuMigrationJobFinish(driver, vm) == 0) { + vm = NULL; + } else if (!virDomainObjIsActive(vm) && + (!vm->persistent || + (ret == 0 && (flags & VIR_MIGRATE_UNDEFINE_SOURCE)))) { + if (flags & VIR_MIGRATE_UNDEFINE_SOURCE) + virDomainDeleteConfig(driver->configDir, driver->autostartDir, vm); + virDomainRemoveInactive(&driver->domains, vm); + vm = NULL; + } + +cleanup: + if (vm) + virDomainObjUnlock(vm); + if (event) + qemuDomainEventQueue(driver, event); + return ret; +} + +/* + * This implements perform phase of v3 migration protocol. + */ +static int +qemuMigrationPerformPhase(struct qemud_driver *driver, + virConnectPtr conn, + virDomainObjPtr vm, + const char *uri, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + unsigned long flags, + const char *dname, + unsigned long resource) +{ + virDomainEventPtr event = NULL; + int ret = -1; + bool resume; + int refs; + + /* If we didn't start the job in the begin phase, start it now. */ + if (!(flags & VIR_MIGRATE_CHANGE_PROTECTION)) { + if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) + goto cleanup; + } else if (!qemuMigrationJobIsActive(vm, QEMU_ASYNC_JOB_MIGRATION_OUT)) { + goto cleanup; + } + + qemuMigrationJobStartPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM3); + + resume = virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING; + ret = doNativeMigrate(driver, vm, uri, cookiein, cookieinlen, + cookieout, cookieoutlen, + flags, dname, resource); + + if (ret < 0 && resume && + virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) { + /* we got here through some sort of failure; start the domain again */ + if (qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_MIGRATION_CANCELED) < 0) { + /* Hm, we already know we are in error here. We don't want to + * overwrite the previous error, though, so we just throw something + * to the logs and hope for the best + */ + VIR_ERROR(_("Failed to resume guest %s after failure"), + vm->def->name); } + + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED_MIGRATED); + } + + if (ret < 0) + goto endjob; + + qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_PERFORM3_DONE); + +endjob: + if (ret < 0) + refs = qemuMigrationJobFinish(driver, vm); + else + refs = qemuMigrationJobContinue(vm); + if (refs == 0) { + vm = NULL; + } else if (!virDomainObjIsActive(vm) && !vm->persistent) { + virDomainRemoveInactive(&driver->domains, vm); + vm = NULL; } cleanup: @@ -2340,6 +2406,61 @@ cleanup: return ret; } +int +qemuMigrationPerform(struct qemud_driver *driver, + virConnectPtr conn, + virDomainObjPtr vm, + const char *xmlin, + const char *dconnuri, + const char *uri, + const char *cookiein, + int cookieinlen, + char **cookieout, + int *cookieoutlen, + unsigned long flags, + const char *dname, + unsigned long resource, + bool v3proto) +{ + VIR_DEBUG("driver=%p, conn=%p, vm=%p, xmlin=%s, dconnuri=%s, " + "uri=%s, cookiein=%s, cookieinlen=%d, cookieout=%p, " + "cookieoutlen=%p, flags=%lx, dname=%s, resource=%lu, v3proto=%d", + driver, conn, vm, NULLSTR(xmlin), NULLSTR(dconnuri), + NULLSTR(uri), NULLSTR(cookiein), cookieinlen, + cookieout, cookieoutlen, flags, NULLSTR(dname), + resource, v3proto); + + if ((flags & (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { + if (cookieinlen) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("received unexpected cookie with P2P migration")); + return -1; + } + + return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, uri, + cookiein, cookieinlen, cookieout, + cookieoutlen, flags, dname, resource, + v3proto); + } else { + if (dconnuri) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Unexpected dconnuri parameter with non-peer2peer migration")); + return -1; + } + + if (v3proto) { + return qemuMigrationPerformPhase(driver, conn, vm, uri, + cookiein, cookieinlen, + cookieout, cookieoutlen, + flags, dname, resource); + } else { + return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, + uri, cookiein, cookieinlen, + cookieout, cookieoutlen, flags, + dname, resource, v3proto); + } + } +} #if WITH_MACVTAP static void @@ -2573,15 +2694,14 @@ int qemuMigrationConfirm(struct qemud_driver *driver, virCheckFlags(QEMU_MIGRATION_FLAGS, -1); + qemuMigrationJobSetPhase(driver, vm, + retcode == 0 + ? QEMU_MIGRATION_PHASE_CONFIRM3 + : QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED); + if (!(mig = qemuMigrationEatCookie(driver, vm, cookiein, cookieinlen, 0))) return -1; - if (!virDomainObjIsActive(vm)) { - qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("guest unexpectedly quit")); - goto cleanup; - } - /* Did the migration go as planned? If yes, kill off the * domain object, but if no, resume CPUs */ diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index 005e415..9e88271 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -34,7 +34,8 @@ VIR_MIGRATE_UNDEFINE_SOURCE | \ VIR_MIGRATE_PAUSED | \ VIR_MIGRATE_NON_SHARED_DISK | \ - VIR_MIGRATE_NON_SHARED_INC) + VIR_MIGRATE_NON_SHARED_INC | \ + VIR_MIGRATE_CHANGE_PROTECTION) enum qemuMigrationJobPhase { QEMU_MIGRATION_PHASE_NONE = 0, -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Make MIGRATION_OUT use the new helper methods.
This also introduces new protection to migration v3 process: the migration job is held from Begin to Confirm to avoid changes to a domain during migration (esp. between Begin and Perform phases). This change is automatically applied to p2p and tunneled migrations. For normal migration, this requires support from a client. In other words, if an old (pre 0.9.4) client starts normal migration of a domain, the domain will not be protected against changes between Begin and Perform steps. --- include/libvirt/libvirt.h.in | 3 + src/libvirt.c | 27 ++++- src/libvirt_internal.h | 6 + src/qemu/qemu_driver.c | 61 +++++++++- src/qemu/qemu_migration.c | 272 ++++++++++++++++++++++++++++++------------ src/qemu/qemu_migration.h | 3 +- 6 files changed, 285 insertions(+), 87 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 607b5bc..18cc521 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -674,6 +674,9 @@ typedef enum { VIR_MIGRATE_NON_SHARED_DISK = (1<< 6), /* migration with non-shared storage with full disk copy */ VIR_MIGRATE_NON_SHARED_INC = (1<< 7), /* migration with non-shared storage with incremental copy */ /* (same base image shared between source and destination) */ + VIR_MIGRATE_CHANGE_PROTECTION = (1<< 8), /* protect for changing domain configuration through the + * whole migration process; this will be used automatically + * if both parties support it */
Nice that the flag is automatic, but:
@@ -3779,10 +3781,14 @@ virDomainMigrateVersion3(virDomainPtr domain, return NULL; }
+ if (VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn, + VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION)) + protection = VIR_MIGRATE_CHANGE_PROTECTION; +
on the converse direction, if the flag is explicitly requested of an 0.9.4 client but you are using an 0.9.3 source, then we should fail the migration rather than let the migration proceed unsafely (for precedence, we reject migrations when PEER2PEER flag is set but the driver lacks the feature). Also, we should document this flag in the comments of the public migrate functions. When the client is managing migration, then you only need support for change protection on the source side; if the destination side lacks it, we don't care. You only checked for the capability on the source side, so for wider compatibility, we should validate the flag, then mask it out before issuing any callbacks to the remote driver; thus an 0.9.4 source and 0.9.3 destination would still benefit from the protection (as it is only the source that needs it) when driven by an 0.9.4 client. [If an 0.9.3 client drives the migration, even with an 0.9.4 source, you don't get the protection, and if the migration isn't using protocol v3, you don't need the protection.] So for once, flag manipulation in libvirt.c is the right thing to do here. Meanwhile, when the source is managing migration (via the peer2peer or direct callbacks), then the flag has to be preserved from client to source, since libvirt.c does not automatically add the flag. In this case, older sources will reject the flag as unknown. In version 3 migration, we should pass the flag to all three source calls in the protocol (you left off confirm), for consistency. I'd like someone to double-check my diff before I push anything, but I think that what I have in the diff below captures the above analysis.
@@ -2340,6 +2406,61 @@ cleanup: return ret; }
+int +qemuMigrationPerform(struct qemud_driver *driver,
Nice way to refactor things (it took me a few reads, not to mention the confusion in the fact that migrationPerform is used to drive both the entire p2p process as well as just a phase within the v2 and v3 process).
+ + if ((flags& (VIR_MIGRATE_TUNNELLED | VIR_MIGRATE_PEER2PEER))) { + if (cookieinlen) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("received unexpected cookie with P2P migration")); + return -1; + } + + return qemuMigrationPerformJob(driver, conn, vm, xmlin, dconnuri, uri, + cookiein, cookieinlen, cookieout, + cookieoutlen, flags, dname, resource, + v3proto);
qemuMigrationPerformJob either does just phase 2 unprotected, or the entire peer-2-peer sequence. But peer2peer is protected by default (since the entire job on the source side was triggered by a single API call). Since we know the source is protected (whether or not the flag is set), and since we don't want to fail when talking to an older destination that doesn't understand the flag, then peer2peer migration should always clear the protection flag (basically, doPeer2PeerMigrate3 is a reimplementation of virDomainMigrateVersion3, and since the former learned how to autoset the bit when needed, so should the latter - it's just that the latter doesn't need to set the bit). I would offer ACK to your patch plus my delta, but it's risky enough that I'd like a second review. Here's my proposed delta: diff --git i/include/libvirt/libvirt.h.in w/include/libvirt/libvirt.h.in index 4b98815..b1bda31 100644 --- i/include/libvirt/libvirt.h.in +++ w/include/libvirt/libvirt.h.in @@ -678,7 +678,7 @@ typedef enum { /* (same base image shared between source and destination) */ VIR_MIGRATE_CHANGE_PROTECTION = (1 << 8), /* protect for changing domain configuration through the * whole migration process; this will be used automatically - * if both parties support it */ + * when supported */ } virDomainMigrateFlags; diff --git i/src/libvirt.c w/src/libvirt.c index 9dc526c..dbef4a5 100644 --- i/src/libvirt.c +++ w/src/libvirt.c @@ -4276,7 +4276,7 @@ confirm: cookieoutlen = 0; ret = domain->conn->driver->domainMigrateConfirm3 (domain, cookiein, cookieinlen, - flags, cancelled); + flags | protection, cancelled); /* If Confirm3 returns -1, there's nothing more we can * do, but fortunately worst case is that there is a * domain left in 'paused' state on source. @@ -4295,7 +4295,7 @@ confirm: /* - * In normal migration, the libvirt client co-ordinates communcation + * In normal migration, the libvirt client co-ordinates communication * between the 2 libvirtd instances on source & dest hosts. * * In this peer-2-peer migration alternative, the libvirt client @@ -4380,7 +4380,7 @@ virDomainMigratePeer2Peer (virDomainPtr domain, /* - * In normal migration, the libvirt client co-ordinates communcation + * In normal migration, the libvirt client co-ordinates communication * between the 2 libvirtd instances on source & dest hosts. * * Some hypervisors support an alternative, direct migration where @@ -4467,6 +4467,9 @@ virDomainMigrateDirect (virDomainPtr domain, * VIR_MIGRATE_UNDEFINE_SOURCE If the migration is successful, undefine the * domain on the source host. * VIR_MIGRATE_PAUSED Leave the domain suspended on the remote side. + * VIR_MIGRATE_CHANGE_PROTECTION Protect against domain configuration + * changes during the migration process (set + * automatically when supported). * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -4574,6 +4577,19 @@ virDomainMigrate (virDomainPtr domain, goto error; } } else { + /* Change protection requires support only on source side, and + * is only needed in v3 migration, which automatically re-adds + * the flag for just the source side. We mask it out for + * non-peer2peer to allow migration from newer source to an + * older destination that rejects the flag. */ + if (flags & VIR_MIGRATE_CHANGE_PROTECTION && + !VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn, + VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION)) { + virLibConnError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("cannot enforce change protection")); + goto error; + } + flags &= ~VIR_MIGRATE_CHANGE_PROTECTION; if (flags & VIR_MIGRATE_TUNNELLED) { virLibConnError(VIR_ERR_OPERATION_INVALID, _("cannot perform tunnelled migration without using peer2peer flag")); @@ -4642,6 +4658,9 @@ error: * VIR_MIGRATE_UNDEFINE_SOURCE If the migration is successful, undefine the * domain on the source host. * VIR_MIGRATE_PAUSED Leave the domain suspended on the remote side. + * VIR_MIGRATE_CHANGE_PROTECTION Protect against domain configuration + * changes during the migration process (set + * automatically when supported). * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -4756,6 +4775,19 @@ virDomainMigrate2(virDomainPtr domain, goto error; } } else { + /* Change protection requires support only on source side, and + * is only needed in v3 migration, which automatically re-adds + * the flag for just the source side. We mask it out for + * non-peer2peer to allow migration from newer source to an + * older destination that rejects the flag. */ + if (flags & VIR_MIGRATE_CHANGE_PROTECTION && + !VIR_DRV_SUPPORTS_FEATURE(domain->conn->driver, domain->conn, + VIR_DRV_FEATURE_MIGRATE_CHANGE_PROTECTION)) { + virLibConnError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("cannot enforce change protection")); + goto error; + } + flags &= ~VIR_MIGRATE_CHANGE_PROTECTION; if (flags & VIR_MIGRATE_TUNNELLED) { virLibConnError(VIR_ERR_OPERATION_INVALID, _("cannot perform tunnelled migration without using peer2peer flag")); @@ -4831,6 +4863,9 @@ error: * on the destination host. * VIR_MIGRATE_UNDEFINE_SOURCE If the migration is successful, undefine the * domain on the source host. + * VIR_MIGRATE_CHANGE_PROTECTION Protect against domain configuration + * changes during the migration process (set + * automatically when supported). * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * If the VIR_MIGRATE_PEER2PEER flag is NOT set, the duri parameter @@ -4952,6 +4987,9 @@ error: * on the destination host. * VIR_MIGRATE_UNDEFINE_SOURCE If the migration is successful, undefine the * domain on the source host. + * VIR_MIGRATE_CHANGE_PROTECTION Protect against domain configuration + * changes during the migration process (set + * automatically when supported). * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * diff --git i/src/qemu/qemu_migration.c w/src/qemu/qemu_migration.c index dca2b16..1121749 100644 --- i/src/qemu/qemu_migration.c +++ w/src/qemu/qemu_migration.c @@ -1994,6 +1994,11 @@ static int doPeer2PeerMigrate3(struct qemud_driver *driver, NULLSTR(dconnuri), NULLSTR(uri), flags, NULLSTR(dname), resource); + /* Unlike the virDomainMigrateVersion3 counterpart, we don't need + * to worry about auto-setting the VIR_MIGRATE_CHANGE_PROTECTION + * bit here, because we are already running inside the context of + * a single job. */ + dom_xml = qemuMigrationBegin(driver, vm, xmlin, &cookieout, &cookieoutlen); if (!dom_xml) @@ -2187,7 +2192,7 @@ static int doPeer2PeerMigrate(struct qemud_driver *driver, p2p = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn, VIR_DRV_FEATURE_MIGRATION_P2P); /* v3proto reflects whether the caller used Perform3, but with - * p2p migrate, regardless of whether Perform3 or Perform3 + * p2p migrate, regardless of whether Perform2 or Perform3 * were used, we decide protocol based on what target supports */ *v3proto = VIR_DRV_SUPPORTS_FEATURE(dconn->driver, dconn, @@ -2207,6 +2212,13 @@ static int doPeer2PeerMigrate(struct qemud_driver *driver, goto cleanup; } + /* Change protection is only required on the source side (us), and + * only for v3 migration when begin and perform are separate jobs. + * But peer-2-peer is already a single job, and we still want to + * talk to older destinations that would reject the flag. + * Therefore it is safe to clear the bit here. */ + flags &= ~VIR_MIGRATE_CHANGE_PROTECTION; + if (*v3proto) ret = doPeer2PeerMigrate3(driver, sconn, dconn, vm, xmlin, dconnuri, uri, flags, dname, resource); -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

--- src/qemu/qemu_process.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 109 insertions(+), 1 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 448b06e..48bd435 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -37,6 +37,7 @@ #include "qemu_hostdev.h" #include "qemu_hotplug.h" #include "qemu_bridge_filter.h" +#include "qemu_migration.h" #if HAVE_NUMACTL # define NUMA_VERSION1_COMPATIBILITY 1 @@ -2236,6 +2237,111 @@ qemuProcessUpdateState(struct qemud_driver *driver, virDomainObjPtr vm) } static int +qemuProcessRecoverMigration(struct qemud_driver *driver, + virDomainObjPtr vm, + virConnectPtr conn, + enum qemuDomainAsyncJob job, + enum qemuMigrationJobPhase phase, + virDomainState state, + int reason) +{ + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) { + switch (phase) { + case QEMU_MIGRATION_PHASE_NONE: + case QEMU_MIGRATION_PHASE_PERFORM2: + case QEMU_MIGRATION_PHASE_BEGIN3: + case QEMU_MIGRATION_PHASE_PERFORM3: + case QEMU_MIGRATION_PHASE_PERFORM3_DONE: + case QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED: + case QEMU_MIGRATION_PHASE_CONFIRM3: + case QEMU_MIGRATION_PHASE_LAST: + break; + + case QEMU_MIGRATION_PHASE_PREPARE: + VIR_DEBUG("Killing unfinished incoming migration for domain %s", + vm->def->name); + return -1; + + case QEMU_MIGRATION_PHASE_FINISH2: + /* source domain is already killed so let's just resume the domain + * and hope we are all set */ + VIR_DEBUG("Incoming migration finished, resuming domain %s", + vm->def->name); + if (qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED) < 0) { + VIR_WARN("Could not resume domain %s", vm->def->name); + } + break; + + case QEMU_MIGRATION_PHASE_FINISH3: + /* migration finished, we started resuming the domain but didn't + * confirm success or failure yet; killing it seems safest */ + VIR_DEBUG("Killing migrated domain %s", vm->def->name); + return -1; + } + } else if (job == QEMU_ASYNC_JOB_MIGRATION_OUT) { + switch (phase) { + case QEMU_MIGRATION_PHASE_NONE: + case QEMU_MIGRATION_PHASE_PREPARE: + case QEMU_MIGRATION_PHASE_FINISH2: + case QEMU_MIGRATION_PHASE_FINISH3: + case QEMU_MIGRATION_PHASE_LAST: + break; + + case QEMU_MIGRATION_PHASE_BEGIN3: + /* nothing happen so far, just forget we were about to migrate the + * domain */ + break; + + case QEMU_MIGRATION_PHASE_PERFORM2: + case QEMU_MIGRATION_PHASE_PERFORM3: + /* migration is still in progress, let's cancel it and resume the + * domain */ + VIR_DEBUG("Canceling unfinished outgoing migration of domain %s", + vm->def->name); + /* TODO cancel possibly running migrate operation */ + /* resume the domain but only if it was paused as a result of + * migration */ + if (state == VIR_DOMAIN_PAUSED && + (reason == VIR_DOMAIN_PAUSED_MIGRATION || + reason == VIR_DOMAIN_PAUSED_UNKNOWN)) { + if (qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED) < 0) { + VIR_WARN("Could not resume domain %s", vm->def->name); + } + } + break; + + case QEMU_MIGRATION_PHASE_PERFORM3_DONE: + /* migration finished but we didn't have a chance to get the result + * of Finish3 step; third party needs to check what to do next + */ + break; + + case QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED: + /* Finish3 failed, we need to resume the domain */ + VIR_DEBUG("Resuming domain %s after failed migration", + vm->def->name); + if (state == VIR_DOMAIN_PAUSED && + (reason == VIR_DOMAIN_PAUSED_MIGRATION || + reason == VIR_DOMAIN_PAUSED_UNKNOWN)) { + if (qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED) < 0) { + VIR_WARN("Could not resume domain %s", vm->def->name); + } + } + break; + + case QEMU_MIGRATION_PHASE_CONFIRM3: + /* migration completed, we need to kill the domain here */ + return -1; + } + } + + return 0; +} + +static int qemuProcessRecoverJob(struct qemud_driver *driver, virDomainObjPtr vm, virConnectPtr conn, @@ -2249,7 +2355,9 @@ qemuProcessRecoverJob(struct qemud_driver *driver, switch (job->asyncJob) { case QEMU_ASYNC_JOB_MIGRATION_OUT: case QEMU_ASYNC_JOB_MIGRATION_IN: - /* we don't know what to do yet */ + if (qemuProcessRecoverMigration(driver, vm, conn, job->asyncJob, + job->phase, state, reason) < 0) + return -1; break; case QEMU_ASYNC_JOB_SAVE: -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
--- src/qemu/qemu_process.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 109 insertions(+), 1 deletions(-)
static int +qemuProcessRecoverMigration(struct qemud_driver *driver, + virDomainObjPtr vm, + virConnectPtr conn, + enum qemuDomainAsyncJob job, + enum qemuMigrationJobPhase phase, + virDomainState state, + int reason) +{ + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) { + switch (phase) { + case QEMU_MIGRATION_PHASE_NONE: + case QEMU_MIGRATION_PHASE_PERFORM2: + case QEMU_MIGRATION_PHASE_BEGIN3:
Should we reject as impossible the phases that should never be encountered on MIGRATION_IN? For example, QEMU_MIGRATION_PHASE_BEGIN3 belongs to MIGRATION_OUT, so if our job is MIGRATION_IN but we see that phase, we should probably fail rather than return 0.
+ case QEMU_MIGRATION_PHASE_PERFORM2: + case QEMU_MIGRATION_PHASE_PERFORM3: + /* migration is still in progress, let's cancel it and resume the + * domain */ + VIR_DEBUG("Canceling unfinished outgoing migration of domain %s", + vm->def->name); + /* TODO cancel possibly running migrate operation */
As in issue qemuMonitorMigrateCancel, but ignoring if it fails? Might be reasonable, but probably as a separate patch.
+ /* resume the domain but only if it was paused as a result of + * migration */ + if (state == VIR_DOMAIN_PAUSED&& + (reason == VIR_DOMAIN_PAUSED_MIGRATION || + reason == VIR_DOMAIN_PAUSED_UNKNOWN)) { + if (qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED)< 0) {
On the other hand, will the monitor command to restart cpus even work if a pending migration is underway? So we may have to do the qemuMonitorMigrateCancel no matter what, to ensure the monitor will let us resume. I think what you have works (strict improvement over what we have now), even if it can be further improved with later patches, so: ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On 07/22/2011 03:06 PM, Eric Blake wrote:
+ case QEMU_MIGRATION_PHASE_PERFORM2: + case QEMU_MIGRATION_PHASE_PERFORM3: + /* migration is still in progress, let's cancel it and resume the + * domain */ + VIR_DEBUG("Canceling unfinished outgoing migration of domain %s", + vm->def->name); + /* TODO cancel possibly running migrate operation */
As in issue qemuMonitorMigrateCancel, but ignoring if it fails? Might be reasonable, but probably as a separate patch.
Should have read the series first - you did this in patch 10/10. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Like other query commands, this can now be called directly during migration. --- src/qemu/qemu_domain.h | 4 ---- src/qemu/qemu_driver.c | 42 ++++++++++++------------------------------ src/qemu/qemu_migration.c | 14 -------------- 3 files changed, 12 insertions(+), 48 deletions(-) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 45fae55..37c9515 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -77,7 +77,6 @@ enum qemuDomainJobSignals { QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME = 1 << 2, /* Request migration downtime change */ QEMU_JOB_SIGNAL_MIGRATE_SPEED = 1 << 3, /* Request migration speed change */ QEMU_JOB_SIGNAL_BLKSTAT = 1 << 4, /* Request blkstat during migration */ - QEMU_JOB_SIGNAL_BLKINFO = 1 << 5, /* Request blkinfo during migration */ }; struct qemuDomainJobSignalsData { @@ -86,9 +85,6 @@ struct qemuDomainJobSignalsData { char *statDevName; /* Device name used by blkstat calls */ virDomainBlockStatsPtr blockStat; /* Block statistics for QEMU_JOB_SIGNAL_BLKSTAT */ int *statRetCode; /* Return code for the blkstat calls */ - char *infoDevName; /* Device name used by blkinfo calls */ - virDomainBlockInfoPtr blockInfo; /* Block information for QEMU_JOB_SIGNAL_BLKINFO */ - int *infoRetCode; /* Return code for the blkinfo calls */ }; struct qemuDomainJobObj { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index b378cb7..407f52f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -6510,39 +6510,21 @@ static int qemuDomainGetBlockInfo(virDomainPtr dom, virDomainObjIsActive(vm)) { qemuDomainObjPrivatePtr priv = vm->privateData; - if ((priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) - || (priv->job.asyncJob == QEMU_ASYNC_JOB_SAVE)) { - virDomainObjRef(vm); - while (priv->job.signals & QEMU_JOB_SIGNAL_BLKINFO) - ignore_value(virCondWait(&priv->job.signalCond, &vm->lock)); - - priv->job.signalsData.infoDevName = disk->info.alias; - priv->job.signalsData.blockInfo = info; - priv->job.signalsData.infoRetCode = &ret; - priv->job.signals |= QEMU_JOB_SIGNAL_BLKINFO; - - while (priv->job.signals & QEMU_JOB_SIGNAL_BLKINFO) - ignore_value(virCondWait(&priv->job.signalCond, &vm->lock)); + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; - if (virDomainObjUnref(vm) == 0) - vm = NULL; + if (virDomainObjIsActive(vm)) { + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ret = qemuMonitorGetBlockExtent(priv->mon, + disk->info.alias, + &info->allocation); + qemuDomainObjExitMonitor(driver, vm); } else { - if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) - goto cleanup; - - if (virDomainObjIsActive(vm)) { - ignore_value(qemuDomainObjEnterMonitor(driver, vm)); - ret = qemuMonitorGetBlockExtent(priv->mon, - disk->info.alias, - &info->allocation); - qemuDomainObjExitMonitor(driver, vm); - } else { - ret = 0; - } - - if (qemuDomainObjEndJob(driver, vm) == 0) - vm = NULL; + ret = 0; } + + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; } else { ret = 0; } diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 3eeb67f..d90219c 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -819,20 +819,6 @@ qemuMigrationProcessJobSignals(struct qemud_driver *driver, if (ret < 0) VIR_WARN("Unable to get block statistics"); - } else if (priv->job.signals & QEMU_JOB_SIGNAL_BLKINFO) { - ret = qemuDomainObjEnterMonitorWithDriver(driver, vm); - if (ret == 0) { - ret = qemuMonitorGetBlockExtent(priv->mon, - priv->job.signalsData.infoDevName, - &priv->job.signalsData.blockInfo->allocation); - qemuDomainObjExitMonitorWithDriver(driver, vm); - } - - *priv->job.signalsData.infoRetCode = ret; - priv->job.signals ^= QEMU_JOB_SIGNAL_BLKINFO; - - if (ret < 0) - VIR_WARN("Unable to get block information"); } else { ret = 0; } -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Like other query commands, this can now be called directly during migration. --- src/qemu/qemu_domain.h | 4 ---- src/qemu/qemu_driver.c | 42 ++++++++++++------------------------------ src/qemu/qemu_migration.c | 14 -------------- 3 files changed, 12 insertions(+), 48 deletions(-)
ACK. In fact, it might be possible to shuffle the series and push this even before the new MIGRATE_CHANGE_PROTECTION stuff, although I haven't tried that yet. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On 07/22/2011 03:10 PM, Eric Blake wrote:
On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Like other query commands, this can now be called directly during migration. --- src/qemu/qemu_domain.h | 4 ---- src/qemu/qemu_driver.c | 42 ++++++++++++------------------------------ src/qemu/qemu_migration.c | 14 -------------- 3 files changed, 12 insertions(+), 48 deletions(-)
ACK. In fact, it might be possible to shuffle the series and push this even before the new MIGRATE_CHANGE_PROTECTION stuff, although I haven't tried that yet.
I've tried it now. This patch can be shuffled, but some of the other cleanups cannot (patch 7/10 introduced QEMU_JOB_MIGRATION_OP and used it in code added earlier by patch 1/10), so I'm not going to do any shuffling. On IRC, danpb told me that he tested the 10 patches as-is with no failures on the libvirt-tck migration behemoth (over 900 tests, with all sorts of cross-compatibility checking), but that he still needed to test my changes to patch 3/10. Once that completes, and he decides whether to ACK my changes, we can get this series pushed, hopefully before 0.9.4-RC2 is built (this series definitely fits in the release, as it is fixing a safety bug without adding any new API, just a new bit to an existing API). -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Like other query commands, this can now be called directly during migration. --- src/qemu/qemu_domain.h | 4 --- src/qemu/qemu_driver.c | 54 +++++++++++++++------------------------------ src/qemu/qemu_migration.c | 18 --------------- 3 files changed, 18 insertions(+), 58 deletions(-) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 37c9515..a3acaf5 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -76,15 +76,11 @@ enum qemuDomainJobSignals { QEMU_JOB_SIGNAL_SUSPEND = 1 << 1, /* Request VM suspend to finish live migration offline */ QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME = 1 << 2, /* Request migration downtime change */ QEMU_JOB_SIGNAL_MIGRATE_SPEED = 1 << 3, /* Request migration speed change */ - QEMU_JOB_SIGNAL_BLKSTAT = 1 << 4, /* Request blkstat during migration */ }; struct qemuDomainJobSignalsData { unsigned long long migrateDowntime; /* Data for QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME */ unsigned long migrateBandwidth; /* Data for QEMU_JOB_SIGNAL_MIGRATE_SPEED */ - char *statDevName; /* Device name used by blkstat calls */ - virDomainBlockStatsPtr blockStat; /* Block statistics for QEMU_JOB_SIGNAL_BLKSTAT */ - int *statRetCode; /* Return code for the blkstat calls */ }; struct qemuDomainJobObj { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 407f52f..f6cd433 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -6056,46 +6056,28 @@ qemudDomainBlockStats (virDomainPtr dom, } priv = vm->privateData; - if ((priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) - || (priv->job.asyncJob == QEMU_ASYNC_JOB_SAVE)) { - virDomainObjRef(vm); - while (priv->job.signals & QEMU_JOB_SIGNAL_BLKSTAT) - ignore_value(virCondWait(&priv->job.signalCond, &vm->lock)); - - priv->job.signalsData.statDevName = disk->info.alias; - priv->job.signalsData.blockStat = stats; - priv->job.signalsData.statRetCode = &ret; - priv->job.signals |= QEMU_JOB_SIGNAL_BLKSTAT; - - while (priv->job.signals & QEMU_JOB_SIGNAL_BLKSTAT) - ignore_value(virCondWait(&priv->job.signalCond, &vm->lock)); - - if (virDomainObjUnref(vm) == 0) - vm = NULL; - } else { - if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) - goto cleanup; + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; - if (!virDomainObjIsActive(vm)) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is not running")); - goto endjob; - } + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } - ignore_value(qemuDomainObjEnterMonitor(driver, vm)); - ret = qemuMonitorGetBlockStatsInfo(priv->mon, - disk->info.alias, - &stats->rd_req, - &stats->rd_bytes, - &stats->wr_req, - &stats->wr_bytes, - &stats->errs); - qemuDomainObjExitMonitor(driver, vm); + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ret = qemuMonitorGetBlockStatsInfo(priv->mon, + disk->info.alias, + &stats->rd_req, + &stats->rd_bytes, + &stats->wr_req, + &stats->wr_bytes, + &stats->errs); + qemuDomainObjExitMonitor(driver, vm); endjob: - if (qemuDomainObjEndJob(driver, vm) == 0) - vm = NULL; - } + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; cleanup: if (vm) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index d90219c..52262e2 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -801,24 +801,6 @@ qemuMigrationProcessJobSignals(struct qemud_driver *driver, } if (ret < 0) VIR_WARN("Unable to set migration speed"); - } else if (priv->job.signals & QEMU_JOB_SIGNAL_BLKSTAT) { - ret = qemuDomainObjEnterMonitorWithDriver(driver, vm); - if (ret == 0) { - ret = qemuMonitorGetBlockStatsInfo(priv->mon, - priv->job.signalsData.statDevName, - &priv->job.signalsData.blockStat->rd_req, - &priv->job.signalsData.blockStat->rd_bytes, - &priv->job.signalsData.blockStat->wr_req, - &priv->job.signalsData.blockStat->wr_bytes, - &priv->job.signalsData.blockStat->errs); - qemuDomainObjExitMonitorWithDriver(driver, vm); - } - - *priv->job.signalsData.statRetCode = ret; - priv->job.signals ^= QEMU_JOB_SIGNAL_BLKSTAT; - - if (ret < 0) - VIR_WARN("Unable to get block statistics"); } else { ret = 0; } -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Like other query commands, this can now be called directly during migration. --- src/qemu/qemu_domain.h | 4 --- src/qemu/qemu_driver.c | 54 +++++++++++++++------------------------------ src/qemu/qemu_migration.c | 18 --------------- 3 files changed, 18 insertions(+), 58 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Call qemu monitor command directly within a special job that is only allowed during outgoing migration. --- src/qemu/qemu_domain.c | 1 + src/qemu/qemu_domain.h | 3 +-- src/qemu/qemu_driver.c | 23 +++++++++++++++-------- src/qemu/qemu_migration.c | 21 +++++---------------- src/qemu/qemu_process.c | 1 + 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d2f03dd..7748592 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -52,6 +52,7 @@ VIR_ENUM_IMPL(qemuDomainJob, QEMU_JOB_LAST, "destroy", "suspend", "modify", + "migration operation", "none", /* async job is never stored in job.active */ "async nested", ); diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index a3acaf5..fa4e182 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -49,6 +49,7 @@ enum qemuDomainJob { QEMU_JOB_DESTROY, /* Destroys the domain (cannot be masked out) */ QEMU_JOB_SUSPEND, /* Suspends (stops vCPUs) the domain */ QEMU_JOB_MODIFY, /* May change state */ + QEMU_JOB_MIGRATION_OP, /* Operation influencing outgoing migration */ /* The following two items must always be the last items before JOB_LAST */ QEMU_JOB_ASYNC, /* Asynchronous job */ @@ -75,12 +76,10 @@ enum qemuDomainJobSignals { QEMU_JOB_SIGNAL_CANCEL = 1 << 0, /* Request job cancellation */ QEMU_JOB_SIGNAL_SUSPEND = 1 << 1, /* Request VM suspend to finish live migration offline */ QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME = 1 << 2, /* Request migration downtime change */ - QEMU_JOB_SIGNAL_MIGRATE_SPEED = 1 << 3, /* Request migration speed change */ }; struct qemuDomainJobSignalsData { unsigned long long migrateDowntime; /* Data for QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME */ - unsigned long migrateBandwidth; /* Data for QEMU_JOB_SIGNAL_MIGRATE_SPEED */ }; struct qemuDomainJobObj { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f6cd433..f0c6489 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7436,19 +7436,23 @@ qemuDomainMigrateSetMaxSpeed(virDomainPtr dom, qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); + qemuDriverUnlock(driver); if (!vm) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(dom->uuid, uuidstr); qemuReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching uuid '%s'"), uuidstr); - goto cleanup; + return -1; } + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MIGRATION_OP) < 0) + goto cleanup; + if (!virDomainObjIsActive(vm)) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain is not running")); - goto cleanup; + goto endjob; } priv = vm->privateData; @@ -7456,18 +7460,21 @@ qemuDomainMigrateSetMaxSpeed(virDomainPtr dom, if (priv->job.asyncJob != QEMU_ASYNC_JOB_MIGRATION_OUT) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain is not being migrated")); - goto cleanup; + goto endjob; } - VIR_DEBUG("Requesting migration speed change to %luMbs", bandwidth); - priv->job.signalsData.migrateBandwidth = bandwidth; - priv->job.signals |= QEMU_JOB_SIGNAL_MIGRATE_SPEED; - ret = 0; + VIR_DEBUG("Setting migration bandwidth to %luMbs", bandwidth); + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ret = qemuMonitorSetMigrationSpeed(priv->mon, bandwidth); + qemuDomainObjExitMonitor(driver, vm); + +endjob: + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; cleanup: if (vm) virDomainObjUnlock(vm); - qemuDriverUnlock(driver); return ret; } diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 52262e2..7c5583b 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -788,19 +788,6 @@ qemuMigrationProcessJobSignals(struct qemud_driver *driver, } if (ret < 0) VIR_WARN("Unable to set migration downtime"); - } else if (priv->job.signals & QEMU_JOB_SIGNAL_MIGRATE_SPEED) { - unsigned long bandwidth = priv->job.signalsData.migrateBandwidth; - - priv->job.signals ^= QEMU_JOB_SIGNAL_MIGRATE_SPEED; - priv->job.signalsData.migrateBandwidth = 0; - VIR_DEBUG("Setting migration bandwidth to %luMbs", bandwidth); - ret = qemuDomainObjEnterMonitorWithDriver(driver, vm); - if (ret == 0) { - ret = qemuMonitorSetMigrationSpeed(priv->mon, bandwidth); - qemuDomainObjExitMonitorWithDriver(driver, vm); - } - if (ret < 0) - VIR_WARN("Unable to set migration speed"); } else { ret = 0; } @@ -2865,10 +2852,12 @@ qemuMigrationJobStart(struct qemud_driver *driver, if (qemuDomainObjBeginAsyncJobWithDriver(driver, vm, job) < 0) return -1; - if (job == QEMU_ASYNC_JOB_MIGRATION_IN) + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) { qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE); - else - qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK); + } else { + qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK | + JOB_MASK(QEMU_JOB_MIGRATION_OP)); + } priv->job.info.type = VIR_DOMAIN_JOB_UNBOUNDED; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 48bd435..732adf6 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2406,6 +2406,7 @@ qemuProcessRecoverJob(struct qemud_driver *driver, */ break; + case QEMU_JOB_MIGRATION_OP: case QEMU_JOB_ASYNC: case QEMU_JOB_ASYNC_NESTED: /* async job was already handled above */ -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Call qemu monitor command directly within a special job that is only allowed during outgoing migration. --- src/qemu/qemu_domain.c | 1 + src/qemu/qemu_domain.h | 3 +-- src/qemu/qemu_driver.c | 23 +++++++++++++++-------- src/qemu/qemu_migration.c | 21 +++++---------------- src/qemu/qemu_process.c | 1 + 5 files changed, 23 insertions(+), 26 deletions(-)
- VIR_DEBUG("Requesting migration speed change to %luMbs", bandwidth); - priv->job.signalsData.migrateBandwidth = bandwidth; - priv->job.signals |= QEMU_JOB_SIGNAL_MIGRATE_SPEED; - ret = 0; + VIR_DEBUG("Setting migration bandwidth to %luMbs", bandwidth); + ignore_value(qemuDomainObjEnterMonitor(driver, vm));
Might be nice to add a comment about why qemuDomainObjEnterMonitor won't fail here (if I understand correctly, it is because we only get this far if priv->job.active represents a migration job, but qemuDomainObjEnterMonitorInternal can only fail if priv->job.active is QEMU_JOB_NONE), but this isn't the first time this file uses ignore_value() without comments. ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Call qemu monitor command directly within a special job that is only allowed during outgoing migration. --- src/qemu/qemu_domain.c | 1 - src/qemu/qemu_domain.h | 6 ------ src/qemu/qemu_driver.c | 23 +++++++++++++++-------- src/qemu/qemu_migration.c | 13 ------------- 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 7748592..4c43e8b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -186,7 +186,6 @@ qemuDomainObjResetAsyncJob(qemuDomainObjPrivatePtr priv) job->start = 0; memset(&job->info, 0, sizeof(job->info)); job->signals = 0; - memset(&job->signalsData, 0, sizeof(job->signalsData)); } void diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index fa4e182..1593257 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -75,11 +75,6 @@ enum qemuDomainAsyncJob { enum qemuDomainJobSignals { QEMU_JOB_SIGNAL_CANCEL = 1 << 0, /* Request job cancellation */ QEMU_JOB_SIGNAL_SUSPEND = 1 << 1, /* Request VM suspend to finish live migration offline */ - QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME = 1 << 2, /* Request migration downtime change */ -}; - -struct qemuDomainJobSignalsData { - unsigned long long migrateDowntime; /* Data for QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME */ }; struct qemuDomainJobObj { @@ -95,7 +90,6 @@ struct qemuDomainJobObj { virCond signalCond; /* Use to coordinate the safe queries during migration */ unsigned int signals; /* Signals for running job */ - struct qemuDomainJobSignalsData signalsData; /* Signal specific data */ }; typedef struct _qemuDomainPCIAddressSet qemuDomainPCIAddressSet; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f0c6489..99fab1a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7387,19 +7387,23 @@ qemuDomainMigrateSetMaxDowntime(virDomainPtr dom, qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); + qemuDriverUnlock(driver); if (!vm) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(dom->uuid, uuidstr); qemuReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching uuid '%s'"), uuidstr); - goto cleanup; + return -1; } + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MIGRATION_OP) < 0) + goto cleanup; + if (!virDomainObjIsActive(vm)) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain is not running")); - goto cleanup; + goto endjob; } priv = vm->privateData; @@ -7407,18 +7411,21 @@ qemuDomainMigrateSetMaxDowntime(virDomainPtr dom, if (priv->job.asyncJob != QEMU_ASYNC_JOB_MIGRATION_OUT) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain is not being migrated")); - goto cleanup; + goto endjob; } - VIR_DEBUG("Requesting migration downtime change to %llums", downtime); - priv->job.signalsData.migrateDowntime = downtime; - priv->job.signals |= QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME; - ret = 0; + VIR_DEBUG("Setting migration downtime to %llums", downtime); + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ret = qemuMonitorSetMigrationDowntime(priv->mon, downtime); + qemuDomainObjExitMonitor(driver, vm); + +endjob: + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; cleanup: if (vm) virDomainObjUnlock(vm); - qemuDriverUnlock(driver); return ret; } diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 7c5583b..dbf0412 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -775,19 +775,6 @@ qemuMigrationProcessJobSignals(struct qemud_driver *driver, VIR_DEBUG("Pausing domain for non-live migration"); if (qemuMigrationSetOffline(driver, vm) < 0) VIR_WARN("Unable to pause domain"); - } else if (priv->job.signals & QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME) { - unsigned long long ms = priv->job.signalsData.migrateDowntime; - - priv->job.signals ^= QEMU_JOB_SIGNAL_MIGRATE_DOWNTIME; - priv->job.signalsData.migrateDowntime = 0; - VIR_DEBUG("Setting migration downtime to %llums", ms); - ret = qemuDomainObjEnterMonitorWithDriver(driver, vm); - if (ret == 0) { - ret = qemuMonitorSetMigrationDowntime(priv->mon, ms); - qemuDomainObjExitMonitorWithDriver(driver, vm); - } - if (ret < 0) - VIR_WARN("Unable to set migration downtime"); } else { ret = 0; } -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
Call qemu monitor command directly within a special job that is only allowed during outgoing migration. --- src/qemu/qemu_domain.c | 1 - src/qemu/qemu_domain.h | 6 ------ src/qemu/qemu_driver.c | 23 +++++++++++++++-------- src/qemu/qemu_migration.c | 13 ------------- 4 files changed, 15 insertions(+), 28 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

--- src/qemu/qemu_domain.h | 1 - src/qemu/qemu_driver.c | 46 ++++++++++++++++++++++---------------------- src/qemu/qemu_migration.c | 6 +---- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 1593257..503b9ad 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -74,7 +74,6 @@ enum qemuDomainAsyncJob { enum qemuDomainJobSignals { QEMU_JOB_SIGNAL_CANCEL = 1 << 0, /* Request job cancellation */ - QEMU_JOB_SIGNAL_SUSPEND = 1 << 1, /* Request VM suspend to finish live migration offline */ }; struct qemuDomainJobObj { diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 99fab1a..6b8cbc9 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1327,6 +1327,8 @@ static int qemudDomainSuspend(virDomainPtr dom) { int ret = -1; virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; + virDomainPausedReason reason; + int eventDetail; qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -1353,34 +1355,32 @@ static int qemudDomainSuspend(virDomainPtr dom) { priv = vm->privateData; if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) { - if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_PAUSED) { - VIR_DEBUG("Requesting domain pause on %s", - vm->def->name); - priv->job.signals |= QEMU_JOB_SIGNAL_SUSPEND; - } - ret = 0; - goto cleanup; + reason = VIR_DOMAIN_PAUSED_MIGRATION; + eventDetail = VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED; } else { - if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_SUSPEND) < 0) - goto cleanup; + reason = VIR_DOMAIN_PAUSED_USER; + eventDetail = VIR_DOMAIN_EVENT_SUSPENDED_PAUSED; + } - if (!virDomainObjIsActive(vm)) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is not running")); + if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_SUSPEND) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto endjob; + } + if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_PAUSED) { + if (qemuProcessStopCPUs(driver, vm, reason) < 0) { goto endjob; } - if (virDomainObjGetState(vm, NULL) != VIR_DOMAIN_PAUSED) { - if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_USER) < 0) { - goto endjob; - } - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); - } - if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0) - goto endjob; - ret = 0; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + eventDetail); } + if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0) + goto endjob; + ret = 0; endjob: if (qemuDomainObjEndJob(driver, vm) == 0) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index dbf0412..bcd020f 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -770,11 +770,6 @@ qemuMigrationProcessJobSignals(struct qemud_driver *driver, if (ret < 0) { VIR_WARN("Unable to cancel job"); } - } else if (priv->job.signals & QEMU_JOB_SIGNAL_SUSPEND) { - priv->job.signals ^= QEMU_JOB_SIGNAL_SUSPEND; - VIR_DEBUG("Pausing domain for non-live migration"); - if (qemuMigrationSetOffline(driver, vm) < 0) - VIR_WARN("Unable to pause domain"); } else { ret = 0; } @@ -2843,6 +2838,7 @@ qemuMigrationJobStart(struct qemud_driver *driver, qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE); } else { qemuDomainObjSetAsyncJobMask(vm, DEFAULT_JOB_MASK | + JOB_MASK(QEMU_JOB_SUSPEND) | JOB_MASK(QEMU_JOB_MIGRATION_OP)); } -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
--- src/qemu/qemu_domain.h | 1 - src/qemu/qemu_driver.c | 46 ++++++++++++++++++++++---------------------- src/qemu/qemu_migration.c | 6 +---- 3 files changed, 24 insertions(+), 29 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

This doesn't abort migration job in any phase, yet. --- src/qemu/qemu_domain.c | 9 +------- src/qemu/qemu_domain.h | 12 +++------- src/qemu/qemu_driver.c | 36 ++++++++++++++++++++++----------- src/qemu/qemu_migration.c | 48 --------------------------------------------- src/qemu/qemu_process.c | 12 +++++++++- 5 files changed, 39 insertions(+), 78 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 4c43e8b..0afa8db 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -52,6 +52,7 @@ VIR_ENUM_IMPL(qemuDomainJob, QEMU_JOB_LAST, "destroy", "suspend", "modify", + "abort", "migration operation", "none", /* async job is never stored in job.active */ "async nested", @@ -158,12 +159,6 @@ qemuDomainObjInitJob(qemuDomainObjPrivatePtr priv) return -1; } - if (virCondInit(&priv->job.signalCond) < 0) { - ignore_value(virCondDestroy(&priv->job.cond)); - ignore_value(virCondDestroy(&priv->job.asyncCond)); - return -1; - } - return 0; } @@ -185,7 +180,6 @@ qemuDomainObjResetAsyncJob(qemuDomainObjPrivatePtr priv) job->mask = DEFAULT_JOB_MASK; job->start = 0; memset(&job->info, 0, sizeof(job->info)); - job->signals = 0; } void @@ -208,7 +202,6 @@ qemuDomainObjFreeJob(qemuDomainObjPrivatePtr priv) { ignore_value(virCondDestroy(&priv->job.cond)); ignore_value(virCondDestroy(&priv->job.asyncCond)); - ignore_value(virCondDestroy(&priv->job.signalCond)); } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 503b9ad..679259f 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -38,7 +38,9 @@ # define JOB_MASK(job) (1 << (job - 1)) # define DEFAULT_JOB_MASK \ - (JOB_MASK(QEMU_JOB_QUERY) | JOB_MASK(QEMU_JOB_DESTROY)) + (JOB_MASK(QEMU_JOB_QUERY) | \ + JOB_MASK(QEMU_JOB_DESTROY) | \ + JOB_MASK(QEMU_JOB_ABORT)) /* Only 1 job is allowed at any time * A job includes *all* monitor commands, even those just querying @@ -49,6 +51,7 @@ enum qemuDomainJob { QEMU_JOB_DESTROY, /* Destroys the domain (cannot be masked out) */ QEMU_JOB_SUSPEND, /* Suspends (stops vCPUs) the domain */ QEMU_JOB_MODIFY, /* May change state */ + QEMU_JOB_ABORT, /* Abort current async job */ QEMU_JOB_MIGRATION_OP, /* Operation influencing outgoing migration */ /* The following two items must always be the last items before JOB_LAST */ @@ -72,10 +75,6 @@ enum qemuDomainAsyncJob { QEMU_ASYNC_JOB_LAST }; -enum qemuDomainJobSignals { - QEMU_JOB_SIGNAL_CANCEL = 1 << 0, /* Request job cancellation */ -}; - struct qemuDomainJobObj { virCond cond; /* Use to coordinate jobs */ enum qemuDomainJob active; /* Currently running job */ @@ -86,9 +85,6 @@ struct qemuDomainJobObj { unsigned long long mask; /* Jobs allowed during async job */ unsigned long long start; /* When the async job started */ virDomainJobInfo info; /* Async job progress data */ - - virCond signalCond; /* Use to coordinate the safe queries during migration */ - unsigned int signals; /* Signals for running job */ }; typedef struct _qemuDomainPCIAddressSet qemuDomainPCIAddressSet; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 6b8cbc9..31748f3 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7347,24 +7347,36 @@ static int qemuDomainAbortJob(virDomainPtr dom) { goto cleanup; } - priv = vm->privateData; + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_ABORT) < 0) + goto cleanup; if (virDomainObjIsActive(vm)) { - if (priv->job.asyncJob) { - VIR_DEBUG("Requesting cancellation of job on vm %s", vm->def->name); - priv->job.signals |= QEMU_JOB_SIGNAL_CANCEL; - } else { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("no job is active on the domain")); - goto cleanup; - } - } else { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("domain is not running")); - goto cleanup; + goto endjob; } - ret = 0; + priv = vm->privateData; + + if (!priv->job.asyncJob) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("no job is active on the domain")); + goto endjob; + } else if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_IN) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("cannot abort incoming migration;" + " use virDomainDestroy instead")); + goto endjob; + } + + VIR_DEBUG("Cancelling job at client request"); + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ret = qemuMonitorMigrateCancel(priv->mon); + qemuDomainObjExitMonitor(driver, vm); + +endjob: + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; cleanup: if (vm) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index bcd020f..e217913 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -743,42 +743,6 @@ qemuMigrationSetOffline(struct qemud_driver *driver, static int -qemuMigrationProcessJobSignals(struct qemud_driver *driver, - virDomainObjPtr vm, - const char *job, - bool cleanup) -{ - qemuDomainObjPrivatePtr priv = vm->privateData; - int ret = -1; - - if (!virDomainObjIsActive(vm)) { - qemuReportError(VIR_ERR_INTERNAL_ERROR, _("%s: %s"), - job, _("guest unexpectedly quit")); - if (cleanup) - priv->job.signals = 0; - return -1; - } - - if (priv->job.signals & QEMU_JOB_SIGNAL_CANCEL) { - priv->job.signals ^= QEMU_JOB_SIGNAL_CANCEL; - VIR_DEBUG("Cancelling job at client request"); - ret = qemuDomainObjEnterMonitorWithDriver(driver, vm); - if (ret == 0) { - ret = qemuMonitorMigrateCancel(priv->mon); - qemuDomainObjExitMonitorWithDriver(driver, vm); - } - if (ret < 0) { - VIR_WARN("Unable to cancel job"); - } - } else { - ret = 0; - } - - return ret; -} - - -static int qemuMigrationUpdateJobStatus(struct qemud_driver *driver, virDomainObjPtr vm, const char *job) @@ -878,17 +842,10 @@ qemuMigrationWaitForCompletion(struct qemud_driver *driver, virDomainObjPtr vm) while (priv->job.info.type == VIR_DOMAIN_JOB_UNBOUNDED) { /* Poll every 50ms for progress & to allow cancellation */ struct timespec ts = { .tv_sec = 0, .tv_nsec = 50 * 1000 * 1000ull }; - while (priv->job.signals) { - if (qemuMigrationProcessJobSignals(driver, vm, job, false) < 0) - goto cleanup; - } - - virCondSignal(&priv->job.signalCond); if (qemuMigrationUpdateJobStatus(driver, vm, job) < 0) goto cleanup; - virDomainObjUnlock(vm); qemuDriverUnlock(driver); @@ -899,11 +856,6 @@ qemuMigrationWaitForCompletion(struct qemud_driver *driver, virDomainObjPtr vm) } cleanup: - while (priv->job.signals) { - qemuMigrationProcessJobSignals(driver, vm, job, true); - } - virCondBroadcast(&priv->job.signalCond); - if (priv->job.info.type == VIR_DOMAIN_JOB_COMPLETED) return 0; else diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 732adf6..45b7053 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2245,6 +2245,8 @@ qemuProcessRecoverMigration(struct qemud_driver *driver, virDomainState state, int reason) { + qemuDomainObjPrivatePtr priv = vm->privateData; + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) { switch (phase) { case QEMU_MIGRATION_PHASE_NONE: @@ -2299,7 +2301,9 @@ qemuProcessRecoverMigration(struct qemud_driver *driver, * domain */ VIR_DEBUG("Canceling unfinished outgoing migration of domain %s", vm->def->name); - /* TODO cancel possibly running migrate operation */ + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ignore_value(qemuMonitorMigrateCancel(priv->mon)); + qemuDomainObjExitMonitor(driver, vm); /* resume the domain but only if it was paused as a result of * migration */ if (state == VIR_DOMAIN_PAUSED && @@ -2347,6 +2351,7 @@ qemuProcessRecoverJob(struct qemud_driver *driver, virConnectPtr conn, const struct qemuDomainJobObj *job) { + qemuDomainObjPrivatePtr priv = vm->privateData; virDomainState state; int reason; @@ -2362,7 +2367,9 @@ qemuProcessRecoverJob(struct qemud_driver *driver, case QEMU_ASYNC_JOB_SAVE: case QEMU_ASYNC_JOB_DUMP: - /* TODO cancel possibly running migrate operation */ + ignore_value(qemuDomainObjEnterMonitor(driver, vm)); + ignore_value(qemuMonitorMigrateCancel(priv->mon)); + qemuDomainObjExitMonitor(driver, vm); /* resume the domain but only if it was paused as a result of * running save/dump operation */ if (state == VIR_DOMAIN_PAUSED && @@ -2407,6 +2414,7 @@ qemuProcessRecoverJob(struct qemud_driver *driver, break; case QEMU_JOB_MIGRATION_OP: + case QEMU_JOB_ABORT: case QEMU_JOB_ASYNC: case QEMU_JOB_ASYNC_NESTED: /* async job was already handled above */ -- 1.7.6

On 07/18/2011 06:27 PM, Jiri Denemark wrote:
This doesn't abort migration job in any phase, yet. --- src/qemu/qemu_domain.c | 9 +------- src/qemu/qemu_domain.h | 12 +++------- src/qemu/qemu_driver.c | 36 ++++++++++++++++++++++----------- src/qemu/qemu_migration.c | 48 --------------------------------------------- src/qemu/qemu_process.c | 12 +++++++++- 5 files changed, 39 insertions(+), 78 deletions(-)
ACK. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On Tue, Jul 19, 2011 at 02:27:29AM +0200, Jiri Denemark wrote:
This is the rest of the original 19 patch series updated with some bugfixes and rebased on current master, which is also available at
https://gitorious.org/~jirka/libvirt/jirka-staging/commits/migration-recover...
I didn't manage to run this through the libvirt-tck migration test but I did some additional testing with the following combinations of libvirt (current contains this series):
I've tested many combinations now with the TCK and it all passes, so an ACK for this series from me. I also tested this with Eric's suggested additions. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 07/27/2011 07:29 AM, Daniel P. Berrange wrote:
On Tue, Jul 19, 2011 at 02:27:29AM +0200, Jiri Denemark wrote:
This is the rest of the original 19 patch series updated with some bugfixes and rebased on current master, which is also available at
https://gitorious.org/~jirka/libvirt/jirka-staging/commits/migration-recover...
I didn't manage to run this through the libvirt-tck migration test but I did some additional testing with the following combinations of libvirt (current contains this series):
I've tested many combinations now with the TCK and it all passes, so an ACK for this series from me. I also tested this with Eric's suggested additions.
I've now pushed the series, with my squash-in for 3/10, and will be shortly submitting a virsh patch to expose the new flag. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org
participants (3)
-
Daniel P. Berrange
-
Eric Blake
-
Jiri Denemark