[libvirt] [PATCH v2 00/15] Implement post-copy migration

(See "Add public APIs for post-copy migration" patch for more details about post-copy migration.) Post-copy support was originally written by Cristian Klein in 2014, but no one touched the series since then. Some patches in this series are modified versions of the old patches from Cristian, some patches had to be rewritten from scratch since libvirt code changed a lot (we started using migration events), and some patches are completely new. While post-copy migration is included in QEMU 2.5.0, it didn't support everything libvirt needs. Thus you need QEMU from git to use post-copy. Luckily, the QEMU migration capability we need to enable to use post-copy is still prefixed with "x-", which means it's still considered experimental. Once we are sure QEMU gives us all we need, the "x-" prefix will be removed. Seamless SPICE migration doesn't work with post-copy now, but I'll look at that. Fetching statistics of a completed post-copy migration does not work either (libvirt would report incorrect data). This series (VIR_MIGRATE_POSTCOPY_AFTER_PRECOPY support) depends on "Introduce migration iteration event" series I sent yesterday. Version 2: - VIR_MIGRATE_POSTCOPY_AFTER_PRECOPY flag dropped - --postcopy-after-precopy implemented entirely in virsh Cristian Klein (6): Add public APIs for post-copy migration qemu: Add QMP functions for post-copy migration qemu: Add support for VIR_MIGRATE_POSTCOPY flag qemu: Implement virDomainMigrateStartPostCopy virsh: Add support for post-copy migration virsh: Add --postcopy-after-precopy option to migrate Jiri Denemark (9): Add event and state details for post-copy qemu: Don't explicitly stop CPUs after migration qemu: Handle postcopy-active migration state virsh: Configurable migrate --timeout action qemu: Don't kill running migrated domain on daemon restart qemu: Refactor qemuProcessRecoverMigration qemu: Handle post-copy migration failures qemu: Refuse to abort migration in post-copy mode qemu: Add flags to qemuMigrationWaitForCompletion examples/object-events/event-test.c | 9 ++ include/libvirt/libvirt-domain.h | 11 ++ src/conf/domain_conf.c | 7 +- src/driver-hypervisor.h | 5 + src/libvirt-domain.c | 114 +++++++++++++++ src/libvirt_public.syms | 4 + src/qemu/qemu_domain.c | 1 + src/qemu/qemu_domain.h | 3 + src/qemu/qemu_driver.c | 72 ++++++++- src/qemu/qemu_migration.c | 283 ++++++++++++++++++++++++++++++------ src/qemu/qemu_migration.h | 6 +- src/qemu/qemu_monitor.c | 16 +- src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 23 +++ src/qemu/qemu_monitor_json.h | 3 + src/qemu/qemu_process.c | 249 +++++++++++++++++++------------ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 13 +- src/remote_protocol-structs | 5 + tools/virsh-domain-monitor.c | 7 +- tools/virsh-domain.c | 158 ++++++++++++++++++-- tools/virsh.pod | 31 +++- 22 files changed, 865 insertions(+), 160 deletions(-) -- 2.7.0

VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY and VIR_DOMAIN_PAUSED_POSTCOPY are used on the source host once migration enters post-copy mode (which means the domain gets paused on the source. After the destination host takes over the execution of the domain, its virtual CPUs are resumed and the domain enters VIR_DOMAIN_RUNNING_POSTCOPY state and VIR_DOMAIN_EVENT_RESUMED_POSTCOPY event is emitted. In case migration fails during post-copy mode and none of the hosts have complete state of the domain, both domains will remain paused with VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason and an upper layer may decide what to do. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change examples/object-events/event-test.c | 9 +++++++++ include/libvirt/libvirt-domain.h | 7 +++++++ src/conf/domain_conf.c | 7 +++++-- tools/virsh-domain-monitor.c | 7 +++++-- tools/virsh-domain.c | 7 +++++-- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/examples/object-events/event-test.c b/examples/object-events/event-test.c index dcae981..70f1186 100644 --- a/examples/object-events/event-test.c +++ b/examples/object-events/event-test.c @@ -159,6 +159,12 @@ static const char *eventDetailToString(int event, int detail) { case VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR: ret = "API error"; break; + case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY: + ret = "Post-copy"; + break; + case VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED: + ret = "Post-copy Error"; + break; } break; case VIR_DOMAIN_EVENT_RESUMED: @@ -172,6 +178,9 @@ static const char *eventDetailToString(int event, int detail) { case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT: ret = "Snapshot"; break; + case VIR_DOMAIN_EVENT_RESUMED_POSTCOPY: + ret = "Post-copy"; + break; } break; case VIR_DOMAIN_EVENT_STOPPED: diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 65f1618..50342d2 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -90,6 +90,7 @@ typedef enum { VIR_DOMAIN_RUNNING_WAKEUP = 8, /* returned from pmsuspended due to wakeup event */ VIR_DOMAIN_RUNNING_CRASHED = 9, /* resumed from crashed */ + VIR_DOMAIN_RUNNING_POSTCOPY = 10, /* running in post-copy migration mode */ # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_RUNNING_LAST @@ -117,6 +118,8 @@ typedef enum { VIR_DOMAIN_PAUSED_SNAPSHOT = 9, /* paused while creating a snapshot */ VIR_DOMAIN_PAUSED_CRASHED = 10, /* paused due to a guest crash */ VIR_DOMAIN_PAUSED_STARTING_UP = 11, /* the domain is being started */ + VIR_DOMAIN_PAUSED_POSTCOPY = 12, /* paused for post-copy migration */ + VIR_DOMAIN_PAUSED_POSTCOPY_FAILED = 13, /* paused after failed post-copy */ # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_PAUSED_LAST @@ -2380,6 +2383,8 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_RESTORED = 4, /* Restored from paused state file */ VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 5, /* Restored from paused snapshot */ VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR = 6, /* suspended after failure during libvirt API call */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7, /* suspended for post-copy migration */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED = 8, /* suspended after failed post-copy */ # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_EVENT_SUSPENDED_LAST @@ -2395,6 +2400,8 @@ typedef enum { VIR_DOMAIN_EVENT_RESUMED_UNPAUSED = 0, /* Normal resume due to admin unpause */ VIR_DOMAIN_EVENT_RESUMED_MIGRATED = 1, /* Resumed for completion of migration */ VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT = 2, /* Resumed from snapshot */ + VIR_DOMAIN_EVENT_RESUMED_POSTCOPY = 3, /* Resumed, but migration is still + running in post-copy mode */ # ifdef VIR_ENUM_SENTINELS VIR_DOMAIN_EVENT_RESUMED_LAST diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index a9706b0..3814d40 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -675,7 +675,8 @@ VIR_ENUM_IMPL(virDomainRunningReason, VIR_DOMAIN_RUNNING_LAST, "migration canceled", "save canceled", "wakeup", - "crashed") + "crashed", + "post-copy") VIR_ENUM_IMPL(virDomainBlockedReason, VIR_DOMAIN_BLOCKED_LAST, "unknown") @@ -692,7 +693,9 @@ VIR_ENUM_IMPL(virDomainPausedReason, VIR_DOMAIN_PAUSED_LAST, "shutdown", "snapshot", "panicked", - "starting up") + "starting up", + "post-copy", + "post-copy failed") VIR_ENUM_IMPL(virDomainShutdownReason, VIR_DOMAIN_SHUTDOWN_LAST, "unknown", diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index 7eb387d..22fe97b 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -182,7 +182,8 @@ VIR_ENUM_IMPL(virshDomainRunningReason, N_("migration canceled"), N_("save canceled"), N_("event wakeup"), - N_("crashed")) + N_("crashed"), + N_("post-copy")) VIR_ENUM_DECL(virshDomainBlockedReason) VIR_ENUM_IMPL(virshDomainBlockedReason, @@ -203,7 +204,9 @@ VIR_ENUM_IMPL(virshDomainPausedReason, N_("shutting down"), N_("creating snapshot"), N_("crashed"), - N_("starting up")) + N_("starting up"), + N_("post-copy"), + N_("post-copy failed")) VIR_ENUM_DECL(virshDomainShutdownReason) VIR_ENUM_IMPL(virshDomainShutdownReason, diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 828ba42..f3f4b8b 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -11400,14 +11400,17 @@ VIR_ENUM_IMPL(virshDomainEventSuspended, N_("Watchdog"), N_("Restored"), N_("Snapshot"), - N_("API error")) + N_("API error"), + N_("Post-copy"), + N_("Post-copy Error")) VIR_ENUM_DECL(virshDomainEventResumed) VIR_ENUM_IMPL(virshDomainEventResumed, VIR_DOMAIN_EVENT_RESUMED_LAST, N_("Unpaused"), N_("Migrated"), - N_("Snapshot")) + N_("Snapshot"), + N_("Post-copy")) VIR_ENUM_DECL(virshDomainEventStopped) VIR_ENUM_IMPL(virshDomainEventStopped, -- 2.7.0

On Thu, Jan 21, 2016 at 11:20:46AM +0100, Jiri Denemark wrote:
VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY and VIR_DOMAIN_PAUSED_POSTCOPY are used on the source host once migration enters post-copy mode (which means the domain gets paused on the source. After the destination host takes over the execution of the domain, its virtual CPUs are resumed and the domain enters VIR_DOMAIN_RUNNING_POSTCOPY state and VIR_DOMAIN_EVENT_RESUMED_POSTCOPY event is emitted.
In case migration fails during post-copy mode and none of the hosts have complete state of the domain, both domains will remain paused with VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason and an upper layer may decide what to do.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
@@ -2380,6 +2383,8 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_RESTORED = 4, /* Restored from paused state file */ VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 5, /* Restored from paused snapshot */ VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR = 6, /* suspended after failure during libvirt API call */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7, /* suspended for post-copy migration */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED = 8, /* suspended after failed post-copy */
Presumably the POSTCOPY_FAILED event can only be emitted on the target, since the source will already be suspended when we see a failure, and it doesn't make sense to issue a suspended event when we're already suspended. ACK Regards, 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 Fri, Jan 22, 2016 at 15:07:04 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:46AM +0100, Jiri Denemark wrote:
VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY and VIR_DOMAIN_PAUSED_POSTCOPY are used on the source host once migration enters post-copy mode (which means the domain gets paused on the source. After the destination host takes over the execution of the domain, its virtual CPUs are resumed and the domain enters VIR_DOMAIN_RUNNING_POSTCOPY state and VIR_DOMAIN_EVENT_RESUMED_POSTCOPY event is emitted.
In case migration fails during post-copy mode and none of the hosts have complete state of the domain, both domains will remain paused with VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason and an upper layer may decide what to do.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
@@ -2380,6 +2383,8 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_RESTORED = 4, /* Restored from paused state file */ VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 5, /* Restored from paused snapshot */ VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR = 6, /* suspended after failure during libvirt API call */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7, /* suspended for post-copy migration */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED = 8, /* suspended after failed post-copy */
Presumably the POSTCOPY_FAILED event can only be emitted on the target, since the source will already be suspended when we see a failure, and it doesn't make sense to issue a suspended event when we're already suspended.
But would it cause any harm? I figured it might be better to emit the event and set the state to POSTCOPY_FAILED even on the source so that apps/users don't have to guess whether POSTCOPY means it's still running or if it already failed. Jirka

On Fri, Jan 22, 2016 at 04:17:42PM +0100, Jiri Denemark wrote:
On Fri, Jan 22, 2016 at 15:07:04 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:46AM +0100, Jiri Denemark wrote:
VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY and VIR_DOMAIN_PAUSED_POSTCOPY are used on the source host once migration enters post-copy mode (which means the domain gets paused on the source. After the destination host takes over the execution of the domain, its virtual CPUs are resumed and the domain enters VIR_DOMAIN_RUNNING_POSTCOPY state and VIR_DOMAIN_EVENT_RESUMED_POSTCOPY event is emitted.
In case migration fails during post-copy mode and none of the hosts have complete state of the domain, both domains will remain paused with VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason and an upper layer may decide what to do.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
@@ -2380,6 +2383,8 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_RESTORED = 4, /* Restored from paused state file */ VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 5, /* Restored from paused snapshot */ VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR = 6, /* suspended after failure during libvirt API call */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7, /* suspended for post-copy migration */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED = 8, /* suspended after failed post-copy */
Presumably the POSTCOPY_FAILED event can only be emitted on the target, since the source will already be suspended when we see a failure, and it doesn't make sense to issue a suspended event when we're already suspended.
But would it cause any harm? I figured it might be better to emit the event and set the state to POSTCOPY_FAILED even on the source so that apps/users don't have to guess whether POSTCOPY means it's still running or if it already failed.
The lifecycle events are supposed to be implementing a state machine, and we're not changing state in this case. I think applications that are currently using libvirt would reasonably consider it an error if libvirt issues an event for a state it is already in, and I could see it causing them to mistakenly run some logic twice if they get two SUSPEND events for the same domain in a row. Regards, 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 Fri, Jan 22, 2016 at 15:23:43 +0000, Daniel P. Berrange wrote:
On Fri, Jan 22, 2016 at 04:17:42PM +0100, Jiri Denemark wrote:
On Fri, Jan 22, 2016 at 15:07:04 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:46AM +0100, Jiri Denemark wrote:
VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY and VIR_DOMAIN_PAUSED_POSTCOPY are used on the source host once migration enters post-copy mode (which means the domain gets paused on the source. After the destination host takes over the execution of the domain, its virtual CPUs are resumed and the domain enters VIR_DOMAIN_RUNNING_POSTCOPY state and VIR_DOMAIN_EVENT_RESUMED_POSTCOPY event is emitted.
In case migration fails during post-copy mode and none of the hosts have complete state of the domain, both domains will remain paused with VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason and an upper layer may decide what to do.
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
@@ -2380,6 +2383,8 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_RESTORED = 4, /* Restored from paused state file */ VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 5, /* Restored from paused snapshot */ VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR = 6, /* suspended after failure during libvirt API call */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY = 7, /* suspended for post-copy migration */ + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED = 8, /* suspended after failed post-copy */
Presumably the POSTCOPY_FAILED event can only be emitted on the target, since the source will already be suspended when we see a failure, and it doesn't make sense to issue a suspended event when we're already suspended.
But would it cause any harm? I figured it might be better to emit the event and set the state to POSTCOPY_FAILED even on the source so that apps/users don't have to guess whether POSTCOPY means it's still running or if it already failed.
The lifecycle events are supposed to be implementing a state machine, and we're not changing state in this case. I think applications that are currently using libvirt would reasonably consider it an error if libvirt issues an event for a state it is already in, and I could see it causing them to mistakenly run some logic twice if they get two SUSPEND events for the same domain in a row.
We already emit some events several times in a row, but I agree it doesn't make sense to add more cases like that. It would actually be a good idea to fix the existing double events (in another patch series in the future). Jirka

From: Cristian Klein <cristiklein@gmail.com> To use post-copy one has to start the migration with VIR_MIGRATE_POSTCOPY flag and, while migration is in progress, call virDomainMigrateStartPostCopy() to switch from pre-copy to post-copy. Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - POSTCOPY_AFTER_PRECOPY flag removed include/libvirt/libvirt-domain.h | 4 ++ src/driver-hypervisor.h | 5 ++ src/libvirt-domain.c | 114 +++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 4 ++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 13 ++++- src/remote_protocol-structs | 5 ++ 7 files changed, 145 insertions(+), 1 deletion(-) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 50342d2..ccbf6a7 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -662,6 +662,7 @@ typedef enum { VIR_MIGRATE_ABORT_ON_ERROR = (1 << 12), /* abort migration on I/O errors happened during migration */ VIR_MIGRATE_AUTO_CONVERGE = (1 << 13), /* force convergence */ VIR_MIGRATE_RDMA_PIN_ALL = (1 << 14), /* RDMA memory pinning */ + VIR_MIGRATE_POSTCOPY = (1 << 15), /* enable (but do not start) post-copy migration */ } virDomainMigrateFlags; @@ -810,6 +811,9 @@ int virDomainMigrateGetMaxSpeed(virDomainPtr domain, unsigned long *bandwidth, unsigned int flags); +int virDomainMigrateStartPostCopy(virDomainPtr domain, + unsigned int flags); + char * virConnectGetDomainCapabilities(virConnectPtr conn, const char *emulatorbin, const char *arch, diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index ae2ec4d..68a7730 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -638,6 +638,10 @@ typedef int const char *dom_xml); typedef int +(*virDrvDomainMigrateStartPostCopy)(virDomainPtr domain, + unsigned int flags); + +typedef int (*virDrvConnectIsEncrypted)(virConnectPtr conn); typedef int @@ -1443,6 +1447,7 @@ struct _virHypervisorDriver { virDrvDomainGetFSInfo domainGetFSInfo; virDrvDomainInterfaceAddresses domainInterfaceAddresses; virDrvDomainSetUserPassword domainSetUserPassword; + virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy; }; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 9491845..676f0f7 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -3537,6 +3537,7 @@ virDomainMigrateUnmanaged(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -3573,6 +3574,11 @@ virDomainMigrateUnmanaged(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -3748,6 +3754,7 @@ virDomainMigrate(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -3784,6 +3791,11 @@ virDomainMigrate(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -3968,6 +3980,11 @@ virDomainMigrate2(virDomainPtr domain, * can use either VIR_MIGRATE_NON_SHARED_DISK or * VIR_MIGRATE_NON_SHARED_INC as they are mutually exclusive. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * There are many limitations on migration imposed by the underlying * technology - for example it may not be possible to migrate between * different processors even with the same architecture, or between @@ -4208,6 +4225,7 @@ int virDomainMigrateUnmanagedCheckCompat(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * If the VIR_MIGRATE_PEER2PEER flag is NOT set, the duri parameter @@ -4240,6 +4258,11 @@ int virDomainMigrateUnmanagedCheckCompat(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -4321,6 +4344,7 @@ virDomainMigrateToURI(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * @@ -4366,6 +4390,11 @@ virDomainMigrateToURI(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -4446,6 +4475,11 @@ virDomainMigrateToURI2(virDomainPtr domain, * can use either VIR_MIGRATE_NON_SHARED_DISK or * VIR_MIGRATE_NON_SHARED_INC as they are mutually exclusive. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * There are many limitations on migration imposed by the underlying * technology - for example it may not be possible to migrate between * different processors even with the same architecture, or between @@ -9163,6 +9197,86 @@ virDomainMigrateGetMaxSpeed(virDomainPtr domain, /** + * virDomainMigrateStartPostCopy: + * @domain: a domain object + * @flags: extra flags; not used yet, so callers should always pass 0 + * + * Starts post-copy migration. This function has to be called while + * migration (initiated with VIR_MIGRATE_POSTCOPY flag) is in progress. + * + * Traditional post-copy migration iteratively walks through guest memory + * pages and migrates those that changed since the previous iteration. The + * iterative phase stops when the number of dirty pages is low enough so that + * the virtual CPUs can be paused, all dirty pages transferred to the + * destination, where the virtual CPUs are unpaused, and all this can happen + * within a predefined downtime period. It's clear that this process may never + * converge if downtime is too short and/or the guest keeps changing a lot of + * memory pages. + * + * When migration is switched to post-copy mode, the virtual CPUs are paused + * immediately, only a minimum set of pages is transferred, and the CPUs are + * unpaused on destination. The source keeps sending all remaining memory pages + * to the destination while the guest is already running there. Whenever the + * guest tries to read a memory page which has not been migrated yet, the + * hypervisor has to tell the source to transfer that page in a priority + * channel. To minimize such page faults, it is a good idea to run at least one + * iteration of pre-copy migration before switching to post-copy. + * + * Post-copy migration is guaranteed to converge since each page is transferred + * at most once no matter how fast it changes. On the other hand once the + * guest is running on the destination host, the migration can no longer be + * rolled back because none of the hosts has complete state. If this happens, + * libvirt will leave the domain paused on both hosts with + * VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason. It's up to the upper layer to + * decide what to do in such case. + * + * The following domain life cycle events are emitted during post-copy + * migration: + * VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY (on the source) -- migration entered + * post-copy mode. + * VIR_DOMAIN_EVENT_RESUMED_POSTCOPY (on the destination) -- the guest is + * running on the destination host while some of its memory pages still + * remain on the source host; neither the source nor the destination host + * contain a complete guest state from this point until migration + * finishes. + * VIR_DOMAIN_EVENT_RESUMED_MIGRATED (on the destination), + * VIR_DOMAIN_EVENT_STOPPED_MIGRATED (on the source) -- migration finished + * successfully and the destination host holds a complete guest state. + * VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED (on either side) -- emitted + * when migration fails in post-copy mode and it's unclear whether any + * of the hosts has a complete guest state. + * + * Returns 0 in case of success, -1 otherwise. + */ +int +virDomainMigrateStartPostCopy(virDomainPtr domain, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(domain); + + virResetLastError(); + + virCheckDomainReturn(domain, -1); + conn = domain->conn; + + virCheckReadOnlyGoto(conn->flags, error); + + if (conn->driver->domainMigrateStartPostCopy) { + if (conn->driver->domainMigrateStartPostCopy(domain, flags) < 0) + goto error; + return 0; + } + + virReportUnsupportedError(); + error: + virDispatchError(conn); + return -1; +} + + +/** * virConnectDomainEventRegisterAny: * @conn: pointer to the connection * @dom: pointer to the domain diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index dd94191..d98414d 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -725,4 +725,8 @@ LIBVIRT_1.2.19 { virDomainRename; } LIBVIRT_1.2.17; +LIBVIRT_1.3.2 { + virDomainMigrateStartPostCopy; +} LIBVIRT_1.2.19; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index d9d7ec8..f0a81a6 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -8462,6 +8462,7 @@ static virHypervisorDriver hypervisor_driver = { .domainInterfaceAddresses = remoteDomainInterfaceAddresses, /* 1.2.14 */ .domainSetUserPassword = remoteDomainSetUserPassword, /* 1.2.16 */ .domainRename = remoteDomainRename, /* 1.2.19 */ + .domainMigrateStartPostCopy = remoteDomainMigrateStartPostCopy, /* 1.3.2 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index bfdbce7..1e1b301 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -3228,6 +3228,11 @@ struct remote_domain_event_callback_migration_iteration_msg { int iteration; }; +struct remote_domain_migrate_start_post_copy_args { + remote_nonnull_domain dom; + unsigned int flags; +}; + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -5706,5 +5711,11 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_DOMAIN_EVENT_CALLBACK_MIGRATION_ITERATION = 359 + REMOTE_PROC_DOMAIN_EVENT_CALLBACK_MIGRATION_ITERATION = 359, + + /** + * @generate: both + * @acl: domain:migrate + */ + REMOTE_PROC_DOMAIN_MIGRATE_START_POST_COPY = 360 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index dff54e8..b372f49 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -2697,6 +2697,10 @@ struct remote_domain_event_callback_migration_iteration_msg { remote_nonnull_domain dom; int iteration; }; +struct remote_domain_migrate_start_post_copy_args { + remote_nonnull_domain dom; + u_int flags; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -3057,4 +3061,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SET_USER_PASSWORD = 357, REMOTE_PROC_DOMAIN_RENAME = 358, REMOTE_PROC_DOMAIN_EVENT_CALLBACK_MIGRATION_ITERATION = 359, + REMOTE_PROC_DOMAIN_MIGRATE_START_POST_COPY = 360, }; -- 2.7.0

On Thu, Jan 21, 2016 at 11:20:47AM +0100, Jiri Denemark wrote:
From: Cristian Klein <cristiklein@gmail.com>
To use post-copy one has to start the migration with VIR_MIGRATE_POSTCOPY flag and, while migration is in progress, call virDomainMigrateStartPostCopy() to switch from pre-copy to post-copy.
Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> ---
Notes: Version 2: - POSTCOPY_AFTER_PRECOPY flag removed
include/libvirt/libvirt-domain.h | 4 ++ src/driver-hypervisor.h | 5 ++ src/libvirt-domain.c | 114 +++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 4 ++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 13 ++++- src/remote_protocol-structs | 5 ++ 7 files changed, 145 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 50342d2..ccbf6a7 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -662,6 +662,7 @@ typedef enum { VIR_MIGRATE_ABORT_ON_ERROR = (1 << 12), /* abort migration on I/O errors happened during migration */ VIR_MIGRATE_AUTO_CONVERGE = (1 << 13), /* force convergence */ VIR_MIGRATE_RDMA_PIN_ALL = (1 << 14), /* RDMA memory pinning */ + VIR_MIGRATE_POSTCOPY = (1 << 15), /* enable (but do not start) post-copy migration */ } virDomainMigrateFlags;
@@ -810,6 +811,9 @@ int virDomainMigrateGetMaxSpeed(virDomainPtr domain, unsigned long *bandwidth, unsigned int flags);
+int virDomainMigrateStartPostCopy(virDomainPtr domain, + unsigned int flags); + char * virConnectGetDomainCapabilities(virConnectPtr conn, const char *emulatorbin, const char *arch, diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index ae2ec4d..68a7730 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -638,6 +638,10 @@ typedef int const char *dom_xml);
typedef int +(*virDrvDomainMigrateStartPostCopy)(virDomainPtr domain, + unsigned int flags); + +typedef int (*virDrvConnectIsEncrypted)(virConnectPtr conn);
typedef int @@ -1443,6 +1447,7 @@ struct _virHypervisorDriver { virDrvDomainGetFSInfo domainGetFSInfo; virDrvDomainInterfaceAddresses domainInterfaceAddresses; virDrvDomainSetUserPassword domainSetUserPassword; + virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy; };
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 9491845..676f0f7 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -3537,6 +3537,7 @@ virDomainMigrateUnmanaged(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -3573,6 +3574,11 @@ virDomainMigrateUnmanaged(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -3748,6 +3754,7 @@ virDomainMigrate(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -3784,6 +3791,11 @@ virDomainMigrate(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -3968,6 +3980,11 @@ virDomainMigrate2(virDomainPtr domain, * can use either VIR_MIGRATE_NON_SHARED_DISK or * VIR_MIGRATE_NON_SHARED_INC as they are mutually exclusive. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * There are many limitations on migration imposed by the underlying * technology - for example it may not be possible to migrate between * different processors even with the same architecture, or between @@ -4208,6 +4225,7 @@ int virDomainMigrateUnmanagedCheckCompat(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * If the VIR_MIGRATE_PEER2PEER flag is NOT set, the duri parameter @@ -4240,6 +4258,11 @@ int virDomainMigrateUnmanagedCheckCompat(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -4321,6 +4344,7 @@ virDomainMigrateToURI(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * @@ -4366,6 +4390,11 @@ virDomainMigrateToURI(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -4446,6 +4475,11 @@ virDomainMigrateToURI2(virDomainPtr domain, * can use either VIR_MIGRATE_NON_SHARED_DISK or * VIR_MIGRATE_NON_SHARED_INC as they are mutually exclusive. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * There are many limitations on migration imposed by the underlying * technology - for example it may not be possible to migrate between * different processors even with the same architecture, or between @@ -9163,6 +9197,86 @@ virDomainMigrateGetMaxSpeed(virDomainPtr domain,
/** + * virDomainMigrateStartPostCopy: + * @domain: a domain object + * @flags: extra flags; not used yet, so callers should always pass 0 + * + * Starts post-copy migration. This function has to be called while + * migration (initiated with VIR_MIGRATE_POSTCOPY flag) is in progress. + * + * Traditional post-copy migration iteratively walks through guest memory + * pages and migrates those that changed since the previous iteration. The + * iterative phase stops when the number of dirty pages is low enough so that + * the virtual CPUs can be paused, all dirty pages transferred to the + * destination, where the virtual CPUs are unpaused, and all this can happen + * within a predefined downtime period. It's clear that this process may never + * converge if downtime is too short and/or the guest keeps changing a lot of + * memory pages. + * + * When migration is switched to post-copy mode, the virtual CPUs are paused + * immediately, only a minimum set of pages is transferred, and the CPUs are + * unpaused on destination. The source keeps sending all remaining memory pages + * to the destination while the guest is already running there. Whenever the + * guest tries to read a memory page which has not been migrated yet, the + * hypervisor has to tell the source to transfer that page in a priority + * channel. To minimize such page faults, it is a good idea to run at least one + * iteration of pre-copy migration before switching to post-copy. + * + * Post-copy migration is guaranteed to converge since each page is transferred + * at most once no matter how fast it changes. On the other hand once the + * guest is running on the destination host, the migration can no longer be + * rolled back because none of the hosts has complete state. If this happens, + * libvirt will leave the domain paused on both hosts with + * VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason. It's up to the upper layer to + * decide what to do in such case. + * + * The following domain life cycle events are emitted during post-copy + * migration: + * VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY (on the source) -- migration entered + * post-copy mode. + * VIR_DOMAIN_EVENT_RESUMED_POSTCOPY (on the destination) -- the guest is + * running on the destination host while some of its memory pages still + * remain on the source host; neither the source nor the destination host + * contain a complete guest state from this point until migration + * finishes. + * VIR_DOMAIN_EVENT_RESUMED_MIGRATED (on the destination), + * VIR_DOMAIN_EVENT_STOPPED_MIGRATED (on the source) -- migration finished + * successfully and the destination host holds a complete guest state. + * VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED (on either side) -- emitted + * when migration fails in post-copy mode and it's unclear whether any + * of the hosts has a complete guest state.
You say either side, but IMHO issuing a suspended event when we are already in a suspended state is not correct, as the VM is not undergoing a lifecycle change in that scenario - you're essentially just using the reason field as a way to report failure which I don't think we should really do. So IMHO POSTCOPY_FAILED is only something to emit on the target. The domain job on the source will show the migration failure in any case, so I don't think its important on the source Regards, 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 Fri, Jan 22, 2016 at 15:10:16 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:47AM +0100, Jiri Denemark wrote:
From: Cristian Klein <cristiklein@gmail.com>
To use post-copy one has to start the migration with VIR_MIGRATE_POSTCOPY flag and, while migration is in progress, call virDomainMigrateStartPostCopy() to switch from pre-copy to post-copy.
Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> ---
Notes: Version 2: - POSTCOPY_AFTER_PRECOPY flag removed
include/libvirt/libvirt-domain.h | 4 ++ src/driver-hypervisor.h | 5 ++ src/libvirt-domain.c | 114 +++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 4 ++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 13 ++++- src/remote_protocol-structs | 5 ++ 7 files changed, 145 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 50342d2..ccbf6a7 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -662,6 +662,7 @@ typedef enum { VIR_MIGRATE_ABORT_ON_ERROR = (1 << 12), /* abort migration on I/O errors happened during migration */ VIR_MIGRATE_AUTO_CONVERGE = (1 << 13), /* force convergence */ VIR_MIGRATE_RDMA_PIN_ALL = (1 << 14), /* RDMA memory pinning */ + VIR_MIGRATE_POSTCOPY = (1 << 15), /* enable (but do not start) post-copy migration */ } virDomainMigrateFlags;
@@ -810,6 +811,9 @@ int virDomainMigrateGetMaxSpeed(virDomainPtr domain, unsigned long *bandwidth, unsigned int flags);
+int virDomainMigrateStartPostCopy(virDomainPtr domain, + unsigned int flags); + char * virConnectGetDomainCapabilities(virConnectPtr conn, const char *emulatorbin, const char *arch, diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index ae2ec4d..68a7730 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -638,6 +638,10 @@ typedef int const char *dom_xml);
typedef int +(*virDrvDomainMigrateStartPostCopy)(virDomainPtr domain, + unsigned int flags); + +typedef int (*virDrvConnectIsEncrypted)(virConnectPtr conn);
typedef int @@ -1443,6 +1447,7 @@ struct _virHypervisorDriver { virDrvDomainGetFSInfo domainGetFSInfo; virDrvDomainInterfaceAddresses domainInterfaceAddresses; virDrvDomainSetUserPassword domainSetUserPassword; + virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy; };
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 9491845..676f0f7 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -3537,6 +3537,7 @@ virDomainMigrateUnmanaged(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -3573,6 +3574,11 @@ virDomainMigrateUnmanaged(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -3748,6 +3754,7 @@ virDomainMigrate(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * VIR_MIGRATE_TUNNELLED requires that VIR_MIGRATE_PEER2PEER be set. * Applications using the VIR_MIGRATE_PEER2PEER flag will probably @@ -3784,6 +3791,11 @@ virDomainMigrate(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -3968,6 +3980,11 @@ virDomainMigrate2(virDomainPtr domain, * can use either VIR_MIGRATE_NON_SHARED_DISK or * VIR_MIGRATE_NON_SHARED_INC as they are mutually exclusive. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * There are many limitations on migration imposed by the underlying * technology - for example it may not be possible to migrate between * different processors even with the same architecture, or between @@ -4208,6 +4225,7 @@ int virDomainMigrateUnmanagedCheckCompat(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * If the VIR_MIGRATE_PEER2PEER flag is NOT set, the duri parameter @@ -4240,6 +4258,11 @@ int virDomainMigrateUnmanagedCheckCompat(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -4321,6 +4344,7 @@ virDomainMigrateToURI(virDomainPtr domain, * automatically when supported). * VIR_MIGRATE_UNSAFE Force migration even if it is considered unsafe. * VIR_MIGRATE_OFFLINE Migrate offline + * VIR_MIGRATE_POSTCOPY Enable (but do not start) post-copy * * The operation of this API hinges on the VIR_MIGRATE_PEER2PEER flag. * @@ -4366,6 +4390,11 @@ virDomainMigrateToURI(virDomainPtr domain, * not support this feature and will return an error if bandwidth * is not 0. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * To see which features are supported by the current hypervisor, * see virConnectGetCapabilities, /capabilities/host/migration_features. * @@ -4446,6 +4475,11 @@ virDomainMigrateToURI2(virDomainPtr domain, * can use either VIR_MIGRATE_NON_SHARED_DISK or * VIR_MIGRATE_NON_SHARED_INC as they are mutually exclusive. * + * Enabling the VIR_MIGRATE_POSTCOPY flag tells libvirt to enable post-copy + * migration. Use virDomainMigrateStartPostCopy to switch migration into + * the post-copy mode. See virDomainMigrateStartPostCopy for more details + * about post-copy. + * * There are many limitations on migration imposed by the underlying * technology - for example it may not be possible to migrate between * different processors even with the same architecture, or between @@ -9163,6 +9197,86 @@ virDomainMigrateGetMaxSpeed(virDomainPtr domain,
/** + * virDomainMigrateStartPostCopy: + * @domain: a domain object + * @flags: extra flags; not used yet, so callers should always pass 0 + * + * Starts post-copy migration. This function has to be called while + * migration (initiated with VIR_MIGRATE_POSTCOPY flag) is in progress. + * + * Traditional post-copy migration iteratively walks through guest memory + * pages and migrates those that changed since the previous iteration. The + * iterative phase stops when the number of dirty pages is low enough so that + * the virtual CPUs can be paused, all dirty pages transferred to the + * destination, where the virtual CPUs are unpaused, and all this can happen + * within a predefined downtime period. It's clear that this process may never + * converge if downtime is too short and/or the guest keeps changing a lot of + * memory pages. + * + * When migration is switched to post-copy mode, the virtual CPUs are paused + * immediately, only a minimum set of pages is transferred, and the CPUs are + * unpaused on destination. The source keeps sending all remaining memory pages + * to the destination while the guest is already running there. Whenever the + * guest tries to read a memory page which has not been migrated yet, the + * hypervisor has to tell the source to transfer that page in a priority + * channel. To minimize such page faults, it is a good idea to run at least one + * iteration of pre-copy migration before switching to post-copy. + * + * Post-copy migration is guaranteed to converge since each page is transferred + * at most once no matter how fast it changes. On the other hand once the + * guest is running on the destination host, the migration can no longer be + * rolled back because none of the hosts has complete state. If this happens, + * libvirt will leave the domain paused on both hosts with + * VIR_DOMAIN_PAUSED_POSTCOPY_FAILED reason. It's up to the upper layer to + * decide what to do in such case. + * + * The following domain life cycle events are emitted during post-copy + * migration: + * VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY (on the source) -- migration entered + * post-copy mode. + * VIR_DOMAIN_EVENT_RESUMED_POSTCOPY (on the destination) -- the guest is + * running on the destination host while some of its memory pages still + * remain on the source host; neither the source nor the destination host + * contain a complete guest state from this point until migration + * finishes. + * VIR_DOMAIN_EVENT_RESUMED_MIGRATED (on the destination), + * VIR_DOMAIN_EVENT_STOPPED_MIGRATED (on the source) -- migration finished + * successfully and the destination host holds a complete guest state. + * VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED (on either side) -- emitted + * when migration fails in post-copy mode and it's unclear whether any + * of the hosts has a complete guest state.
You say either side, but IMHO issuing a suspended event when we are already in a suspended state is not correct, as the VM is not undergoing a lifecycle change in that scenario - you're essentially just using the reason field as a way to report failure which I don't think we should really do.
So IMHO POSTCOPY_FAILED is only something to emit on the target.
The domain job on the source will show the migration failure in any case, so I don't think its important on the source
Thinking about it a bit more... you're right, setting POSTCOPY_FAILED reason for PAUSED state makes sense even on the source, but the event is redundant there. Jirka

With a very old QEMU which doesn't support events we need to explicitly call qemuMigrationSetOffline at the end of migration to update our internal state. On the other hand, if we talk to QEMU using QMP, we should just wait for the STOP event and let the event handler update the state and trigger a libvirt event. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_domain.h | 2 ++ src/qemu/qemu_migration.c | 27 ++++++++++++++++----------- src/qemu/qemu_process.c | 20 +++++++++++++++----- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 7fc4fff..9fd9076 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -203,6 +203,8 @@ struct _qemuDomainObjPrivate { bool signalIOError; /* true if the domain condition should be signalled on I/O error */ + bool signalStop; /* true if the domain condition should be signalled on + QMP STOP event */ }; # define QEMU_DOMAIN_DISK_PRIVATE(disk) \ diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 51e7125..c927888 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -4563,19 +4563,24 @@ qemuMigrationRun(virQEMUDriverPtr driver, else if (rc == -1) goto cleanup; - /* When migration completed, QEMU will have paused the - * CPUs for us, but unless we're using the JSON monitor - * we won't have been notified of this, so might still - * think we're running. For v2 protocol this doesn't - * matter because we'll kill the VM soon, but for v3 - * this is important because we stay paused until the - * confirm3 step, but need to release the lock state + /* When migration completed, QEMU will have paused the CPUs for us. + * Wait for the STOP event to be processed or explicitly stop CPUs + * (for old QEMU which does not send events) to release the lock state. */ - if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { - if (qemuMigrationSetOffline(driver, vm) < 0) { - priv->job.current->type = VIR_DOMAIN_JOB_FAILED; - goto cleanup; + if (priv->monJSON) { + while (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + priv->signalStop = true; + rc = virDomainObjWait(vm); + priv->signalStop = false; + if (rc < 0) { + priv->job.current->type = VIR_DOMAIN_JOB_FAILED; + goto cleanup; + } } + } else if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING && + qemuMigrationSetOffline(driver, vm) < 0) { + priv->job.current->type = VIR_DOMAIN_JOB_FAILED; + goto cleanup; } ret = 0; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index d465b4f..cab1aee 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -701,6 +701,8 @@ qemuProcessHandleStop(qemuMonitorPtr mon ATTRIBUTE_UNUSED, { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; + virDomainPausedReason reason = VIR_DOMAIN_PAUSED_UNKNOWN; + virDomainEventSuspendedDetailType detail = VIR_DOMAIN_EVENT_SUSPENDED_PAUSED; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); @@ -712,16 +714,24 @@ qemuProcessHandleStop(qemuMonitorPtr mon ATTRIBUTE_UNUSED, goto unlock; } - VIR_DEBUG("Transitioned guest %s to paused state", - vm->def->name); + if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) { + reason = VIR_DOMAIN_PAUSED_MIGRATION; + detail = VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED; + } + + VIR_DEBUG("Transitioned guest %s to paused state, reason %s", + vm->def->name, virDomainPausedReasonTypeToString(reason)); if (priv->job.current) ignore_value(virTimeMillisNow(&priv->job.current->stopped)); - virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_UNKNOWN); + if (priv->signalStop) + virDomainObjBroadcast(vm); + + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, reason); event = virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); + VIR_DOMAIN_EVENT_SUSPENDED, + detail); VIR_FREE(priv->lockState); if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0) -- 2.7.0

Migration enters "postcopy-active" state after QEMU switches to post-copy and pauses guest CPUs. From libvirt's point of view this state is similar to "completed" because we need to transfer guest execution to the destination host. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_migration.c | 84 +++++++++++++++++++++++++++++++++++++------- src/qemu/qemu_monitor.c | 2 +- src/qemu/qemu_monitor.h | 1 + src/qemu/qemu_monitor_json.c | 1 + src/qemu/qemu_process.c | 10 ++++-- 5 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index c927888..6c98c5c 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -2549,6 +2549,7 @@ qemuMigrationUpdateJobType(qemuDomainJobInfoPtr jobInfo) case QEMU_MONITOR_MIGRATION_STATUS_SETUP: case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE: + case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY: case QEMU_MONITOR_MIGRATION_STATUS_CANCELLING: case QEMU_MONITOR_MIGRATION_STATUS_LAST: break; @@ -2666,6 +2667,7 @@ enum qemuMigrationCompletedFlags { QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR = (1 << 0), QEMU_MIGRATION_COMPLETED_CHECK_STORAGE = (1 << 1), QEMU_MIGRATION_COMPLETED_UPDATE_STATS = (1 << 2), + QEMU_MIGRATION_COMPLETED_POSTCOPY = (1 << 3), }; /** @@ -2707,6 +2709,20 @@ qemuMigrationCompleted(virQEMUDriverPtr driver, goto error; } + /* In case of postcopy the source considers migration completed at the + * moment it switched from active to postcopy-active state. The destination + * will continue waiting until the migrate state changes to completed. + */ + if (flags & QEMU_MIGRATION_COMPLETED_POSTCOPY && + jobInfo->type == VIR_DOMAIN_JOB_UNBOUNDED && + jobInfo->stats.status == QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY) { + VIR_DEBUG("Migration switched to post-copy"); + if (updateStats && + qemuMigrationUpdateJobStatus(driver, vm, asyncJob) < 0) + goto error; + return 1; + } + if (jobInfo->type == VIR_DOMAIN_JOB_COMPLETED) return 1; else @@ -2740,9 +2756,11 @@ qemuMigrationWaitForCompletion(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; qemuDomainJobInfoPtr jobInfo = priv->job.current; bool events = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT); - unsigned int flags = QEMU_MIGRATION_COMPLETED_UPDATE_STATS; + unsigned int flags; int rv; + flags = QEMU_MIGRATION_COMPLETED_UPDATE_STATS | + QEMU_MIGRATION_COMPLETED_POSTCOPY; if (abort_on_error) flags |= QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR; if (storage) @@ -2781,9 +2799,11 @@ qemuMigrationWaitForCompletion(virQEMUDriverPtr driver, static int qemuMigrationWaitForDestCompletion(virQEMUDriverPtr driver, virDomainObjPtr vm, - qemuDomainAsyncJob asyncJob) + qemuDomainAsyncJob asyncJob, + bool postcopy) { qemuDomainObjPrivatePtr priv = vm->privateData; + unsigned int flags = 0; int rv; if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT)) @@ -2791,7 +2811,11 @@ qemuMigrationWaitForDestCompletion(virQEMUDriverPtr driver, VIR_DEBUG("Waiting for incoming migration to complete"); - while ((rv = qemuMigrationCompleted(driver, vm, asyncJob, NULL, 0)) != 1) { + if (postcopy) + flags = QEMU_MIGRATION_COMPLETED_POSTCOPY; + + while ((rv = qemuMigrationCompleted(driver, vm, asyncJob, + NULL, flags)) != 1) { if (rv < 0 || virDomainObjWait(vm) < 0) return -1; } @@ -2992,7 +3016,7 @@ qemuMigrationRunIncoming(virQEMUDriverPtr driver, goto cleanup; } - if (qemuMigrationWaitForDestCompletion(driver, vm, asyncJob) < 0) + if (qemuMigrationWaitForDestCompletion(driver, vm, asyncJob, false) < 0) goto cleanup; ret = 0; @@ -4378,6 +4402,7 @@ qemuMigrationRun(virQEMUDriverPtr driver, unsigned int cookieFlags = 0; bool abort_on_error = !!(flags & VIR_MIGRATE_ABORT_ON_ERROR); bool events = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT); + bool inPostCopy = false; int rc; VIR_DEBUG("driver=%p, vm=%p, cookiein=%s, cookieinlen=%d, " @@ -4563,6 +4588,9 @@ qemuMigrationRun(virQEMUDriverPtr driver, else if (rc == -1) goto cleanup; + if (priv->job.current->stats.status == QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY) + inPostCopy = true; + /* When migration completed, QEMU will have paused the CPUs for us. * Wait for the STOP event to be processed or explicitly stop CPUs * (for old QEMU which does not send events) to release the lock state. @@ -4572,15 +4600,12 @@ qemuMigrationRun(virQEMUDriverPtr driver, priv->signalStop = true; rc = virDomainObjWait(vm); priv->signalStop = false; - if (rc < 0) { - priv->job.current->type = VIR_DOMAIN_JOB_FAILED; - goto cleanup; - } + if (rc < 0) + goto cancelPostCopy; } } else if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING && qemuMigrationSetOffline(driver, vm) < 0) { - priv->job.current->type = VIR_DOMAIN_JOB_FAILED; - goto cleanup; + goto cancelPostCopy; } ret = 0; @@ -4609,7 +4634,7 @@ qemuMigrationRun(virQEMUDriverPtr driver, ignore_value(virTimeMillisNow(&priv->job.completed->sent)); } - if (priv->job.current->type == VIR_DOMAIN_JOB_UNBOUNDED) + if (priv->job.current->type == VIR_DOMAIN_JOB_UNBOUNDED && !inPostCopy) priv->job.current->type = VIR_DOMAIN_JOB_FAILED; cookieFlags |= QEMU_MIGRATION_COOKIE_NETWORK | @@ -4649,6 +4674,13 @@ qemuMigrationRun(virQEMUDriverPtr driver, } } goto cleanup; + + cancelPostCopy: + priv->job.current->type = VIR_DOMAIN_JOB_FAILED; + if (inPostCopy) + goto cancel; + else + goto cleanup; } /* Perform migration using QEMU's native migrate support, @@ -5800,6 +5832,7 @@ qemuMigrationFinish(virQEMUDriverPtr driver, unsigned short port; unsigned long long timeReceived = 0; virObjectEventPtr event; + bool inPostCopy = false; VIR_DEBUG("driver=%p, dconn=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, retcode=%d", @@ -5891,7 +5924,8 @@ qemuMigrationFinish(virQEMUDriverPtr driver, * before starting guest CPUs. */ if (qemuMigrationWaitForDestCompletion(driver, vm, - QEMU_ASYNC_JOB_MIGRATION_IN) < 0) { + QEMU_ASYNC_JOB_MIGRATION_IN, + !!(flags & VIR_MIGRATE_POSTCOPY)) < 0) { /* There's not much we can do for v2 protocol since the * original domain on the source host is already gone. */ @@ -5899,13 +5933,17 @@ qemuMigrationFinish(virQEMUDriverPtr driver, goto endjob; } + if (priv->job.current->stats.status == QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY) + inPostCopy = true; + if (!(flags & VIR_MIGRATE_PAUSED)) { /* run 'cont' on the destination, which allows migration on qemu * >= 0.10.6 to work properly. This isn't strictly necessary on * older qemu's, but it also doesn't hurt anything there */ if (qemuProcessStartCPUs(driver, vm, dconn, - VIR_DOMAIN_RUNNING_MIGRATED, + inPostCopy ? VIR_DOMAIN_RUNNING_POSTCOPY + : VIR_DOMAIN_RUNNING_MIGRATED, QEMU_ASYNC_JOB_MIGRATION_IN) < 0) { if (virGetLastError() == NULL) virReportError(VIR_ERR_INTERNAL_ERROR, @@ -5926,6 +5964,13 @@ qemuMigrationFinish(virQEMUDriverPtr driver, if (v3proto) goto endjob; } + + if (inPostCopy) { + event = virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_RESUMED, + VIR_DOMAIN_EVENT_RESUMED_POSTCOPY); + qemuDomainEventQueue(driver, event); + } } if (mig->jobInfo) { @@ -5942,6 +5987,19 @@ qemuMigrationFinish(virQEMUDriverPtr driver, qemuDomainJobInfoUpdateDowntime(priv->job.completed); } + if (inPostCopy) { + if (qemuMigrationWaitForDestCompletion(driver, vm, + QEMU_ASYNC_JOB_MIGRATION_IN, + false) < 0) { + goto endjob; + } + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + virDomainObjSetState(vm, + VIR_DOMAIN_RUNNING, + VIR_DOMAIN_RUNNING_MIGRATED); + } + } + dom = virGetDomain(dconn, vm->def->name, vm->def->uuid); event = virDomainEventLifecycleNewFromObj(vm, diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index 6b23e88..edb3310 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -161,7 +161,7 @@ VIR_ONCE_GLOBAL_INIT(qemuMonitor) VIR_ENUM_IMPL(qemuMonitorMigrationStatus, QEMU_MONITOR_MIGRATION_STATUS_LAST, "inactive", "setup", - "active", + "active", "postcopy-active", "completed", "failed", "cancelling", "cancelled") diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 9d7d5f3..420d82c 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -467,6 +467,7 @@ typedef enum { QEMU_MONITOR_MIGRATION_STATUS_INACTIVE, QEMU_MONITOR_MIGRATION_STATUS_SETUP, QEMU_MONITOR_MIGRATION_STATUS_ACTIVE, + QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY, QEMU_MONITOR_MIGRATION_STATUS_COMPLETED, QEMU_MONITOR_MIGRATION_STATUS_ERROR, QEMU_MONITOR_MIGRATION_STATUS_CANCELLING, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 24a8865..8d71dd4 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2493,6 +2493,7 @@ qemuMonitorJSONGetMigrationStatsReply(virJSONValuePtr reply, break; case QEMU_MONITOR_MIGRATION_STATUS_ACTIVE: + case QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY: case QEMU_MONITOR_MIGRATION_STATUS_COMPLETED: case QEMU_MONITOR_MIGRATION_STATUS_CANCELLING: ram = virJSONValueObjectGetObject(ret, "ram"); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index cab1aee..5ced279 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -715,8 +715,14 @@ qemuProcessHandleStop(qemuMonitorPtr mon ATTRIBUTE_UNUSED, } if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) { - reason = VIR_DOMAIN_PAUSED_MIGRATION; - detail = VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED; + if (priv->job.current->stats.status == + QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY) { + reason = VIR_DOMAIN_PAUSED_POSTCOPY; + detail = VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY; + } else { + reason = VIR_DOMAIN_PAUSED_MIGRATION; + detail = VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED; + } } VIR_DEBUG("Transitioned guest %s to paused state, reason %s", -- 2.7.0

On Thu, Jan 21, 2016 at 11:20:49AM +0100, Jiri Denemark wrote:
Migration enters "postcopy-active" state after QEMU switches to post-copy and pauses guest CPUs. From libvirt's point of view this state is similar to "completed" because we need to transfer guest execution to the destination host.
It is the same 'completed' from te POV of internal migration API calls..
From the end users POV though it is the same as migration active, since we've not finished migration.
I can't tell from the code, but the virDomainJobInfo should still report that migration is active, and not completed. Is it doing so ? Regards, 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 Fri, Jan 22, 2016 at 15:15:03 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:49AM +0100, Jiri Denemark wrote:
Migration enters "postcopy-active" state after QEMU switches to post-copy and pauses guest CPUs. From libvirt's point of view this state is similar to "completed" because we need to transfer guest execution to the destination host.
It is the same 'completed' from te POV of internal migration API calls..
From the end users POV though it is the same as migration active, since we've not finished migration.
I can't tell from the code, but the virDomainJobInfo should still report that migration is active, and not completed. Is it doing so ?
Yes, it's only the same from our internal POV. Querying job status will still report the migration as active and the progress counters will be updated until migration really completes. The following change takes care of that: - if (priv->job.current->type == VIR_DOMAIN_JOB_UNBOUNDED) + if (priv->job.current->type == VIR_DOMAIN_JOB_UNBOUNDED && !inPostCopy) priv->job.current->type = VIR_DOMAIN_JOB_FAILED; Jirka

From: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: The migration capability is called x-postcopy-ram in QEMU, which means it's still considered experimental. The main reason for it is to make sure the feature is complete from libvirt's point of view. The "x-" prefix will be removed once we have a working implementation. Version 2: - no change src/qemu/qemu_monitor.c | 14 +++++++++++++- src/qemu/qemu_monitor.h | 3 +++ src/qemu/qemu_monitor_json.c | 22 ++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 3 +++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index edb3310..d34878b 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -167,7 +167,8 @@ VIR_ENUM_IMPL(qemuMonitorMigrationStatus, VIR_ENUM_IMPL(qemuMonitorMigrationCaps, QEMU_MONITOR_MIGRATION_CAPS_LAST, - "xbzrle", "auto-converge", "rdma-pin-all", "events") + "xbzrle", "auto-converge", "rdma-pin-all", "events", + "x-postcopy-ram") VIR_ENUM_IMPL(qemuMonitorVMStatus, QEMU_MONITOR_VM_STATUS_LAST, @@ -3817,3 +3818,14 @@ qemuMonitorMigrateIncoming(qemuMonitorPtr mon, return qemuMonitorJSONMigrateIncoming(mon, uri); } + + +int +qemuMonitorMigrateStartPostCopy(qemuMonitorPtr mon) +{ + VIR_DEBUG("mon=%p", mon); + + QEMU_CHECK_MONITOR_JSON(mon); + + return qemuMonitorJSONMigrateStartPostCopy(mon); +} diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index 420d82c..a655437 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -526,6 +526,7 @@ typedef enum { QEMU_MONITOR_MIGRATION_CAPS_AUTO_CONVERGE, QEMU_MONITOR_MIGRATION_CAPS_RDMA_PIN_ALL, QEMU_MONITOR_MIGRATION_CAPS_EVENTS, + QEMU_MONITOR_MIGRATION_CAPS_POSTCOPY, QEMU_MONITOR_MIGRATION_CAPS_LAST } qemuMonitorMigrationCaps; @@ -944,6 +945,8 @@ int qemuMonitorGetMemoryDeviceInfo(qemuMonitorPtr mon, int qemuMonitorMigrateIncoming(qemuMonitorPtr mon, const char *uri); +int qemuMonitorMigrateStartPostCopy(qemuMonitorPtr mon); + /** * When running two dd process and using <> redirection, we need a * shell that will not truncate files. These two strings serve that diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 8d71dd4..463ec4e 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -6682,3 +6682,25 @@ qemuMonitorJSONMigrateIncoming(qemuMonitorPtr mon, virJSONValueFree(reply); return ret; } + + +int +qemuMonitorJSONMigrateStartPostCopy(qemuMonitorPtr mon) +{ + int ret = -1; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + + if (!(cmd = qemuMonitorJSONMakeCommand("migrate-start-postcopy", NULL))) + return -1; + + if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) + goto cleanup; + + ret = qemuMonitorJSONCheckError(cmd, reply); + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 2c27c6f..99effa4 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -485,4 +485,7 @@ int qemuMonitorJSONMigrateIncoming(qemuMonitorPtr mon, const char *uri) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int qemuMonitorJSONMigrateStartPostCopy(qemuMonitorPtr mon) + ATTRIBUTE_NONNULL(1); + #endif /* QEMU_MONITOR_JSON_H */ -- 2.7.0

On Thu, Jan 21, 2016 at 11:20:50AM +0100, Jiri Denemark wrote:
From: Cristian Klein <cristiklein@gmail.com>
Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> ---
Notes: The migration capability is called x-postcopy-ram in QEMU, which means it's still considered experimental. The main reason for it is to make sure the feature is complete from libvirt's point of view. The "x-" prefix will be removed once we have a working implementation.
So if we decide this patchset is OK, will QEMU remove the 'x-' prefix from their code for 2.6 ? It would be desirable not to merge code in libvirt that uses the 'x-' prefix at all. Regards, 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 Fri, Jan 22, 2016 at 15:16:20 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:50AM +0100, Jiri Denemark wrote:
From: Cristian Klein <cristiklein@gmail.com>
Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> ---
Notes: The migration capability is called x-postcopy-ram in QEMU, which means it's still considered experimental. The main reason for it is to make sure the feature is complete from libvirt's point of view. The "x-" prefix will be removed once we have a working implementation.
So if we decide this patchset is OK, will QEMU remove the 'x-' prefix from their code for 2.6 ? It would be desirable not to merge code in libvirt that uses the 'x-' prefix at all.
Right, that's the plan. Jirka

On Fri, Jan 22, 2016 at 04:30:44PM +0100, Jiri Denemark wrote:
On Fri, Jan 22, 2016 at 15:16:20 +0000, Daniel P. Berrange wrote:
On Thu, Jan 21, 2016 at 11:20:50AM +0100, Jiri Denemark wrote:
From: Cristian Klein <cristiklein@gmail.com>
Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> ---
Notes: The migration capability is called x-postcopy-ram in QEMU, which means it's still considered experimental. The main reason for it is to make sure the feature is complete from libvirt's point of view. The "x-" prefix will be removed once we have a working implementation.
So if we decide this patchset is OK, will QEMU remove the 'x-' prefix from their code for 2.6 ? It would be desirable not to merge code in libvirt that uses the 'x-' prefix at all.
Right, that's the plan.
Ok, well you're obviously OK with the series, and I think the QEMU interactions are fine too. So we might as well tell them now to go ahead and remove the x- prefix on the basis that its fine for libvirt. Regards, 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 :|

From: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_migration.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_migration.h | 3 +- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 6c98c5c..64c1eb8 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -2498,6 +2498,54 @@ qemuMigrationSetPinAll(virQEMUDriverPtr driver, return ret; } + +static int +qemuMigrationSetPostCopy(virQEMUDriverPtr driver, + virDomainObjPtr vm, + bool state, + qemuDomainAsyncJob job) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + int ret; + + if (qemuDomainObjEnterMonitorAsync(driver, vm, job) < 0) + return -1; + + ret = qemuMonitorGetMigrationCapability( + priv->mon, + QEMU_MONITOR_MIGRATION_CAPS_POSTCOPY); + + if (ret < 0) { + goto cleanup; + } else if (ret == 0 && !state) { + /* Unsupported but we want it off anyway */ + goto cleanup; + } else if (ret == 0) { + if (job == QEMU_ASYNC_JOB_MIGRATION_IN) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("Post-copy migration is not supported by " + "target QEMU binary")); + } else { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("Post-copy migration is not supported by " + "source QEMU binary")); + } + ret = -1; + goto cleanup; + } + + ret = qemuMonitorSetMigrationCapability( + priv->mon, + QEMU_MONITOR_MIGRATION_CAPS_POSTCOPY, + state); + + cleanup: + if (qemuDomainObjExitMonitor(driver, vm) < 0) + ret = -1; + return ret; +} + + static int qemuMigrationWaitForSpice(virDomainObjPtr vm) { @@ -3132,6 +3180,15 @@ qemuMigrationBeginPhase(virQEMUDriverPtr driver, !qemuMigrationIsSafe(vm->def, nmigrate_disks, migrate_disks)) goto cleanup; + if (flags & VIR_MIGRATE_POSTCOPY && + (!(flags & VIR_MIGRATE_LIVE) || + flags & VIR_MIGRATE_PAUSED)) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("post-copy migration is not supported with non-live " + "or paused migration")); + goto cleanup; + } + if (flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC)) { bool has_drive_mirror = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_DRIVE_MIRROR); @@ -3471,6 +3528,15 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, cookieFlags = QEMU_MIGRATION_COOKIE_GRAPHICS; } + if (flags & VIR_MIGRATE_POSTCOPY && + (!(flags & VIR_MIGRATE_LIVE) || + flags & VIR_MIGRATE_PAUSED)) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("post-copy migration is not supported with non-live " + "or paused migration")); + goto cleanup; + } + if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; @@ -3621,6 +3687,11 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver, QEMU_ASYNC_JOB_MIGRATION_IN) < 0) goto stopjob; + if (qemuMigrationSetPostCopy(driver, vm, + flags & VIR_MIGRATE_POSTCOPY, + QEMU_ASYNC_JOB_MIGRATION_IN) < 0) + goto stopjob; + if (mig->nbd && flags & (VIR_MIGRATE_NON_SHARED_DISK | VIR_MIGRATE_NON_SHARED_INC) && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NBD_SERVER)) { @@ -4487,6 +4558,11 @@ qemuMigrationRun(virQEMUDriverPtr driver, QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) goto cleanup; + if (qemuMigrationSetPostCopy(driver, vm, + flags & VIR_MIGRATE_POSTCOPY, + QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) + goto cleanup; + if (qemuDomainObjEnterMonitorAsync(driver, vm, QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) goto cleanup; diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index 2445e13..e30b2ec 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -41,7 +41,8 @@ VIR_MIGRATE_COMPRESSED | \ VIR_MIGRATE_ABORT_ON_ERROR | \ VIR_MIGRATE_AUTO_CONVERGE | \ - VIR_MIGRATE_RDMA_PIN_ALL) + VIR_MIGRATE_RDMA_PIN_ALL | \ + VIR_MIGRATE_POSTCOPY) /* All supported migration parameters and their types. */ # define QEMU_MIGRATION_PARAMETERS \ -- 2.7.0

From: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_domain.c | 1 + src/qemu/qemu_domain.h | 1 + src/qemu/qemu_driver.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_migration.c | 2 ++ 4 files changed, 62 insertions(+) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 0fa2dbe..15a9aa8 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -188,6 +188,7 @@ qemuDomainObjResetAsyncJob(qemuDomainObjPrivatePtr priv) job->dump_memory_only = false; job->abortJob = false; job->spiceMigrated = false; + job->postcopyEnabled = false; VIR_FREE(job->current); } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index 9fd9076..2bea482 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -138,6 +138,7 @@ struct qemuDomainJobObj { qemuDomainJobInfoPtr completed; /* statistics data of a recently completed job */ bool abortJob; /* abort of the job requested */ bool spiceMigrated; /* spice migration completed */ + bool postcopyEnabled; /* post-copy migration was enabled */ }; typedef void (*qemuDomainCleanupCallback)(virQEMUDriverPtr driver, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8ccf68b..c62561b 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -13421,6 +13421,63 @@ qemuDomainMigrateGetMaxSpeed(virDomainPtr dom, } +static int +qemuDomainMigrateStartPostCopy(virDomainPtr dom, + unsigned int flags) +{ + virQEMUDriverPtr driver = dom->conn->privateData; + virDomainObjPtr vm; + qemuDomainObjPrivatePtr priv; + int ret = -1; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromDomain(dom))) + goto cleanup; + + if (virDomainMigrateStartPostCopyEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MIGRATION_OP) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto endjob; + } + + priv = vm->privateData; + + if (priv->job.asyncJob != QEMU_ASYNC_JOB_MIGRATION_OUT) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("post-copy can only be started while " + "outgoing migration is in progress")); + goto endjob; + } + + if (!priv->job.postcopyEnabled) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("switching to post-copy requires migration to be " + "started with VIR_MIGRATE_POSTCOPY flag")); + goto endjob; + } + + VIR_DEBUG("Starting post-copy"); + qemuDomainObjEnterMonitor(driver, vm); + ret = qemuMonitorMigrateStartPostCopy(priv->mon); + if (qemuDomainObjExitMonitor(driver, vm) < 0) + ret = -1; + + endjob: + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + typedef enum { VIR_DISK_CHAIN_NO_ACCESS, VIR_DISK_CHAIN_READ_ONLY, @@ -20222,6 +20279,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainInterfaceAddresses = qemuDomainInterfaceAddresses, /* 1.2.14 */ .domainSetUserPassword = qemuDomainSetUserPassword, /* 1.2.16 */ .domainRename = qemuDomainRename, /* 1.2.19 */ + .domainMigrateStartPostCopy = qemuDomainMigrateStartPostCopy, /* 1.3.2 */ }; diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 64c1eb8..c5bd2fc 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -2542,6 +2542,8 @@ qemuMigrationSetPostCopy(virQEMUDriverPtr driver, cleanup: if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; + if (ret == 0) + priv->job.postcopyEnabled = state; return ret; } -- 2.7.0

From: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change tools/virsh-domain.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 11 ++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index f3f4b8b..bcb6809 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9623,6 +9623,10 @@ static const vshCmdOptDef opts_migrate[] = { .type = VSH_OT_BOOL, .help = N_("abort on soft errors during migration") }, + {.name = "postcopy", + .type = VSH_OT_BOOL, + .help = N_("enable post-copy migration; switch to it using migrate-postcopy command") + }, {.name = "migrateuri", .type = VSH_OT_STRING, .help = N_("migration URI, usually can be omitted") @@ -9789,6 +9793,9 @@ doMigrate(void *opaque) if (vshCommandOptBool(cmd, "abort-on-error")) flags |= VIR_MIGRATE_ABORT_ON_ERROR; + if (vshCommandOptBool(cmd, "postcopy")) + flags |= VIR_MIGRATE_POSTCOPY; + if (flags & VIR_MIGRATE_PEER2PEER || vshCommandOptBool(cmd, "direct")) { if (virDomainMigrateToURI3(dom, desturi, params, nparams, flags) == 0) ret = '0'; @@ -10092,6 +10099,48 @@ cmdMigrateGetMaxSpeed(vshControl *ctl, const vshCmd *cmd) } /* + * "migrate-postcopy" command + */ +static const vshCmdInfo info_migrate_postcopy[] = { + {.name = "help", + .data = N_("Switch running migration from pre-copy to post-copy") + }, + {.name = "desc", + .data = N_("Switch running migration from pre-copy to post-copy. " + "The migration must have been started with --postcopy option.") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_migrate_postcopy[] = { + {.name = "domain", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("domain name, id or uuid") + }, + {.name = NULL} +}; + +static bool +cmdMigratePostCopy(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + bool ret = false; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (virDomainMigrateStartPostCopy(dom, 0) < 0) + goto cleanup; + + ret = true; + + cleanup: + virDomainFree(dom); + return ret; +} + +/* * "domdisplay" command */ static const vshCmdInfo info_domdisplay[] = { @@ -12902,6 +12951,12 @@ const vshCmdDef domManagementCmds[] = { .info = info_migrate_getspeed, .flags = 0 }, + {.name = "migrate-postcopy", + .handler = cmdMigratePostCopy, + .opts = opts_migrate_postcopy, + .info = info_migrate_postcopy, + .flags = 0 + }, {.name = "numatune", .handler = cmdNumatune, .opts = opts_numatune, diff --git a/tools/virsh.pod b/tools/virsh.pod index e830c59..109c608 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1529,7 +1529,7 @@ to the I<uri> namespace is displayed instead of being modified. =item B<migrate> [I<--live>] [I<--offline>] [I<--direct>] [I<--p2p> [I<--tunnelled>]] [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--unsafe>] [I<--verbose>] -[I<--compressed>] [I<--abort-on-error>] [I<--auto-converge>] +[I<--compressed>] [I<--abort-on-error>] [I<--auto-converge>] [I<--postcopy>] I<domain> I<desturi> [I<migrateuri>] [I<graphicsuri>] [I<listen-address>] [I<dname>] [I<--timeout> B<seconds>] [I<--xml> B<file>] [I<--migrate-disks> B<disk-list>] @@ -1559,6 +1559,10 @@ of migration. I<--compressed> activates compression of memory pages that have to be transferred repeatedly during live migration. I<--abort-on-error> cancels the migration if a soft error (for example I/O error) happens during the migration. I<--auto-converge> forces convergence during live migration. +I<--postcopy> enables post-copy logic in migration, but does not +actually start post-copy, i.e., migration is started in pre-copy mode. +Once migration is running, the user may switch to post-copy using the +B<migrate-postcopy> command sent from another virsh instance. B<Note>: Individual hypervisors usually do not support all possible types of migration. For example, QEMU does not support direct migration. @@ -1689,6 +1693,11 @@ reject the value or convert it to the maximum value allowed. Get the maximum migration bandwidth (in MiB/s) for a domain. +=item B<migrate-postcopy> I<domain> + +Switch the current migration from pre-copy to post-copy. This is only +supported for a migration started with I<--postcopy> option. + =item B<numatune> I<domain> [I<--mode> B<mode>] [I<--nodeset> B<nodeset>] [[I<--config>] [I<--live>] | [I<--current>]] -- 2.7.0

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change tools/virsh-domain.c | 63 +++++++++++++++++++++++++++++++++++++++++++++------- tools/virsh.pod | 15 ++++++++----- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index bcb6809..96125bb 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9645,7 +9645,16 @@ static const vshCmdOptDef opts_migrate[] = { }, {.name = "timeout", .type = VSH_OT_INT, - .help = N_("force guest to suspend if live migration exceeds timeout (in seconds)") + .help = N_("run action specified by --timeout-* option (suspend by " + "default) if live migration exceeds timeout (in seconds)") + }, + {.name = "timeout-suspend", + .type = VSH_OT_BOOL, + .help = N_("suspend the guest after timeout") + }, + {.name = "timeout-postcopy", + .type = VSH_OT_BOOL, + .help = N_("switch to post-copy after timeout") }, {.name = "xml", .type = VSH_OT_STRING, @@ -9823,14 +9832,35 @@ doMigrate(void *opaque) goto out; } +typedef enum { + VIRSH_MIGRATE_TIMEOUT_DEFAULT, + VIRSH_MIGRATE_TIMEOUT_SUSPEND, + VIRSH_MIGRATE_TIMEOUT_POSTCOPY, +} virshMigrateTimeoutAction; + static void -virshMigrationTimeout(vshControl *ctl, - virDomainPtr dom, - void *opaque ATTRIBUTE_UNUSED) +virshMigrateTimeout(vshControl *ctl, + virDomainPtr dom, + void *opaque) { - vshDebug(ctl, VSH_ERR_DEBUG, "suspending the domain, " - "since migration timed out\n"); - virDomainSuspend(dom); + virshMigrateTimeoutAction action = *(virshMigrateTimeoutAction *) opaque; + + switch (action) { + case VIRSH_MIGRATE_TIMEOUT_DEFAULT: /* unreachable */ + case VIRSH_MIGRATE_TIMEOUT_SUSPEND: + vshDebug(ctl, VSH_ERR_DEBUG, + "migration timed out; suspending domain\n"); + if (virDomainSuspend(dom) < 0) + vshDebug(ctl, VSH_ERR_INFO, "suspending domain failed\n"); + break; + + case VIRSH_MIGRATE_TIMEOUT_POSTCOPY: + vshDebug(ctl, VSH_ERR_DEBUG, + "migration timed out; switching to post-copy\n"); + if (virDomainMigrateStartPostCopy(dom, 0) < 0) + vshDebug(ctl, VSH_ERR_INFO, "switching to post-copy failed\n"); + break; + } } static bool @@ -9842,9 +9872,12 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) bool verbose = false; bool functionReturn = false; int timeout = 0; + virshMigrateTimeoutAction timeoutAction = VIRSH_MIGRATE_TIMEOUT_DEFAULT; bool live_flag = false; virshCtrlData data = { .dconn = NULL }; + VSH_EXCLUSIVE_OPTIONS("timeout-suspend", "timeout-postcopy"); + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) return false; @@ -9861,6 +9894,19 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) goto cleanup; } + if (vshCommandOptBool(cmd, "timeout-suspend")) + timeoutAction = VIRSH_MIGRATE_TIMEOUT_SUSPEND; + if (vshCommandOptBool(cmd, "timeout-postcopy")) + timeoutAction = VIRSH_MIGRATE_TIMEOUT_POSTCOPY; + if (timeout > 0) { + if (timeoutAction == VIRSH_MIGRATE_TIMEOUT_DEFAULT) + timeoutAction = VIRSH_MIGRATE_TIMEOUT_SUSPEND; + } else if (timeoutAction) { + vshError(ctl, "%s", + _("migrate: Unexpected --timeout-* option without --timeout")); + goto cleanup; + } + if (pipe(p) < 0) goto cleanup; @@ -9891,7 +9937,8 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) &data) < 0) goto cleanup; functionReturn = virshWatchJob(ctl, dom, verbose, p[0], timeout, - virshMigrationTimeout, NULL, _("Migration")); + virshMigrateTimeout, + &timeoutAction, _("Migration")); virThreadJoin(&workerThread); diff --git a/tools/virsh.pod b/tools/virsh.pod index 109c608..64b4287 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1531,8 +1531,8 @@ to the I<uri> namespace is displayed instead of being modified. [I<--copy-storage-inc>] [I<--change-protection>] [I<--unsafe>] [I<--verbose>] [I<--compressed>] [I<--abort-on-error>] [I<--auto-converge>] [I<--postcopy>] I<domain> I<desturi> [I<migrateuri>] [I<graphicsuri>] [I<listen-address>] -[I<dname>] [I<--timeout> B<seconds>] [I<--xml> B<file>] -[I<--migrate-disks> B<disk-list>] +[I<dname>] [I<--timeout> B<seconds> [I<--timeout-suspend> | I<--timeout-postcopy>]] +[I<--xml> B<file>] [I<--migrate-disks> B<disk-list>] Migrate domain to another host. Add I<--live> for live migration; <--p2p> for peer-2-peer migration; I<--direct> for direct migration; or I<--tunnelled> @@ -1582,9 +1582,14 @@ the destination to supply a larger set of changes to any host-specific portions of the domain XML, such as accounting for naming differences between source and destination in accessing underlying storage. -I<--timeout> B<seconds> forces guest to suspend when live migration exceeds -that many seconds, and -then the migration will complete offline. It can only be used with I<--live>. +I<--timeout> B<seconds> tells virsh to run a specified action when live +migration exceeds that many seconds. It can only be used with I<--live>. +If I<--timeout-suspend> is specified, the domain will be suspended after +the timeout and the migration will complete offline; this is the default +if no I<--timeout-*> option is specified on the command line. When +I<--timeout-postcopy> is used, virsh will switch migration from pre-copy +to post-copy upon timeout; migration has to be started with I<--postcopy> +option for this to work. Running migration can be canceled by interrupting virsh (usually using C<Ctrl-C>) or by B<domjobabort> command sent from another virsh instance. -- 2.7.0

From: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Cristian Klein <cristiklein@gmail.com> Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - implement --postcopy-after-precopy entirely in virsh tools/virsh-domain.c | 35 +++++++++++++++++++++++++++++++++++ tools/virsh.pod | 9 ++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 96125bb..75a9f38 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -9627,6 +9627,10 @@ static const vshCmdOptDef opts_migrate[] = { .type = VSH_OT_BOOL, .help = N_("enable post-copy migration; switch to it using migrate-postcopy command") }, + {.name = "postcopy-after-precopy", + .type = VSH_OT_BOOL, + .help = N_("automatically switch to post-copy migration after one pass of pre-copy") + }, {.name = "migrateuri", .type = VSH_OT_STRING, .help = N_("migration URI, usually can be omitted") @@ -9863,6 +9867,23 @@ virshMigrateTimeout(vshControl *ctl, } } +static void +virshMigrateIteration(virConnectPtr conn ATTRIBUTE_UNUSED, + virDomainPtr dom, + int iteration, + void *opaque) +{ + vshControl *ctl = opaque; + + if (iteration == 2) { + vshDebug(ctl, VSH_ERR_DEBUG, + "iteration %d finished; switching to post-copy\n", + iteration - 1); + if (virDomainMigrateStartPostCopy(dom, 0) < 0) + vshDebug(ctl, VSH_ERR_INFO, "switching to post-copy failed\n"); + } +} + static bool cmdMigrate(vshControl *ctl, const vshCmd *cmd) { @@ -9875,6 +9896,8 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) virshMigrateTimeoutAction timeoutAction = VIRSH_MIGRATE_TIMEOUT_DEFAULT; bool live_flag = false; virshCtrlData data = { .dconn = NULL }; + virshControlPtr priv = ctl->privData; + int iterEvent = -1; VSH_EXCLUSIVE_OPTIONS("timeout-suspend", "timeout-postcopy"); @@ -9907,6 +9930,16 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) goto cleanup; } + if (vshCommandOptBool(cmd, "postcopy-after-precopy")) { + iterEvent = virConnectDomainEventRegisterAny( + priv->conn, dom, + VIR_DOMAIN_EVENT_ID_MIGRATION_ITERATION, + VIR_DOMAIN_EVENT_CALLBACK(virshMigrateIteration), + ctl, NULL); + if (iterEvent < 0) + goto cleanup; + } + if (pipe(p) < 0) goto cleanup; @@ -9945,6 +9978,8 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) cleanup: if (data.dconn) virConnectClose(data.dconn); + if (iterEvent != -1) + virConnectDomainEventDeregisterAny(priv->conn, iterEvent); virDomainFree(dom); VIR_FORCE_CLOSE(p[0]); VIR_FORCE_CLOSE(p[1]); diff --git a/tools/virsh.pod b/tools/virsh.pod index 64b4287..2ace976 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1530,8 +1530,9 @@ to the I<uri> namespace is displayed instead of being modified. [I<--persistent>] [I<--undefinesource>] [I<--suspend>] [I<--copy-storage-all>] [I<--copy-storage-inc>] [I<--change-protection>] [I<--unsafe>] [I<--verbose>] [I<--compressed>] [I<--abort-on-error>] [I<--auto-converge>] [I<--postcopy>] -I<domain> I<desturi> [I<migrateuri>] [I<graphicsuri>] [I<listen-address>] -[I<dname>] [I<--timeout> B<seconds> [I<--timeout-suspend> | I<--timeout-postcopy>]] +[I<--postcopy-after-precopy>] I<domain> I<desturi> [I<migrateuri>] +[I<graphicsuri>] [I<listen-address>] [I<dname>] +[I<--timeout> B<seconds> [I<--timeout-suspend> | I<--timeout-postcopy>]] [I<--xml> B<file>] [I<--migrate-disks> B<disk-list>] Migrate domain to another host. Add I<--live> for live migration; <--p2p> @@ -1562,7 +1563,9 @@ migration. I<--auto-converge> forces convergence during live migration. I<--postcopy> enables post-copy logic in migration, but does not actually start post-copy, i.e., migration is started in pre-copy mode. Once migration is running, the user may switch to post-copy using the -B<migrate-postcopy> command sent from another virsh instance. +B<migrate-postcopy> command sent from another virsh instance or use +I<--postcopy-after-precopy> to let libvirt automatically switch to +post-copy after the first pass of pre-copy is finished. B<Note>: Individual hypervisors usually do not support all possible types of migration. For example, QEMU does not support direct migration. -- 2.7.0

When destination libvirtd is restarted during migration in Finish phase just after the point we started guest CPUs, we should not kill the domain. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_process.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 5ced279..879cd12 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -3304,9 +3304,13 @@ qemuProcessRecoverMigration(virQEMUDriverPtr driver, 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; + * confirm success or failure yet; killing it seems safest unless + * we already started guest CPUs */ + if (state != VIR_DOMAIN_RUNNING) { + VIR_DEBUG("Killing migrated domain %s", vm->def->name); + return -1; + } + break; } } else if (job == QEMU_ASYNC_JOB_MIGRATION_OUT) { switch (phase) { -- 2.7.0

Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_process.c | 206 ++++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 96 deletions(-) diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 879cd12..a0c93c9 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -3265,113 +3265,122 @@ qemuProcessUpdateState(virQEMUDriverPtr driver, virDomainObjPtr vm) } static int -qemuProcessRecoverMigration(virQEMUDriverPtr driver, - virDomainObjPtr vm, - virConnectPtr conn, - qemuDomainAsyncJob job, - qemuMigrationJobPhase phase, - virDomainState state, - int reason) +qemuProcessRecoverMigrationIn(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virConnectPtr conn, + qemuMigrationJobPhase phase, + virDomainState state, + int reason ATTRIBUTE_UNUSED) { - 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; + 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: + /* N/A for incoming migration */ + break; - case QEMU_MIGRATION_PHASE_PREPARE: - VIR_DEBUG("Killing unfinished incoming migration for domain %s", - vm->def->name); + 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, + QEMU_ASYNC_JOB_NONE) < 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 unless + * we already started guest CPUs */ + if (state != VIR_DOMAIN_RUNNING) { + VIR_DEBUG("Killing migrated domain %s", vm->def->name); return -1; + } + break; + } - 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, - QEMU_ASYNC_JOB_NONE) < 0) { - VIR_WARN("Could not resume domain %s", vm->def->name); - } - break; + return 0; +} - case QEMU_MIGRATION_PHASE_FINISH3: - /* migration finished, we started resuming the domain but didn't - * confirm success or failure yet; killing it seems safest unless - * we already started guest CPUs */ - if (state != VIR_DOMAIN_RUNNING) { - VIR_DEBUG("Killing migrated domain %s", vm->def->name); - return -1; - } - break; +static int +qemuProcessRecoverMigrationOut(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virConnectPtr conn, + qemuMigrationJobPhase phase, + virDomainState state, + int reason) +{ + 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: + /* N/A for outgoing migration */ + break; + + case QEMU_MIGRATION_PHASE_BEGIN3: + /* nothing happened 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("Cancelling unfinished migration of domain %s", + vm->def->name); + if (qemuMigrationCancel(driver, vm) < 0) { + VIR_WARN("Could not cancel ongoing migration of domain %s", + vm->def->name); } - } 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; + goto resume; - case QEMU_MIGRATION_PHASE_BEGIN3: - /* nothing happen so far, just forget we were about to migrate the - * domain */ - 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_PERFORM2: - case QEMU_MIGRATION_PHASE_PERFORM3: - /* migration is still in progress, let's cancel it and resume the - * domain */ - if (qemuMigrationCancel(driver, vm) < 0) - return -1; - /* 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, - QEMU_ASYNC_JOB_NONE) < 0) { - VIR_WARN("Could not resume domain %s", vm->def->name); - } - } - 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); + goto resume; - 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: + /* migration completed, we need to kill the domain here */ + return -1; + } - 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, - QEMU_ASYNC_JOB_NONE) < 0) { - VIR_WARN("Could not resume domain %s", vm->def->name); - } - } - break; + return 0; - case QEMU_MIGRATION_PHASE_CONFIRM3: - /* migration completed, we need to kill the domain here */ - return -1; + resume: + /* 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, + QEMU_ASYNC_JOB_NONE) < 0) { + VIR_WARN("Could not resume domain %s", vm->def->name); } } - return 0; } @@ -3389,9 +3398,14 @@ qemuProcessRecoverJob(virQEMUDriverPtr driver, switch (job->asyncJob) { case QEMU_ASYNC_JOB_MIGRATION_OUT: + if (qemuProcessRecoverMigrationOut(driver, vm, conn, job->phase, + state, reason) < 0) + return -1; + break; + case QEMU_ASYNC_JOB_MIGRATION_IN: - if (qemuProcessRecoverMigration(driver, vm, conn, job->asyncJob, - job->phase, state, reason) < 0) + if (qemuProcessRecoverMigrationIn(driver, vm, conn, job->phase, + state, reason) < 0) return -1; break; -- 2.7.0

When migration fails in the post-copy mode, it's impossible to just kill the destination domain and resume the source since the source no longer contains current guest state. Let's mark domains on both sides as VIR_DOMAIN_PAUSED_POSTCOPY_FAILED to let the upper layer decide what to do with them. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_migration.c | 87 +++++++++++++++++++++++++++++++++++++++-------- src/qemu/qemu_migration.h | 3 ++ src/qemu/qemu_process.c | 59 ++++++++++++++++++++++++-------- 3 files changed, 119 insertions(+), 30 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index c5bd2fc..2a34b4a 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1475,14 +1475,21 @@ qemuMigrationRestoreDomainState(virConnectPtr conn, virDomainObjPtr vm) { virQEMUDriverPtr driver = conn->privateData; qemuDomainObjPrivatePtr priv = vm->privateData; - int state = virDomainObjGetState(vm, NULL); + int reason; + virDomainState state = virDomainObjGetState(vm, &reason); bool ret = false; - VIR_DEBUG("driver=%p, vm=%p, pre-mig-state=%d, state=%d", - driver, vm, priv->preMigrationState, state); + VIR_DEBUG("driver=%p, vm=%p, pre-mig-state=%s, state=%s, reason=%s", + driver, vm, + virDomainStateTypeToString(priv->preMigrationState), + virDomainStateTypeToString(state), + virDomainStateReasonToString(state, reason)); - if (state == VIR_DOMAIN_PAUSED && - priv->preMigrationState == VIR_DOMAIN_RUNNING) { + if (state != VIR_DOMAIN_PAUSED || + reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED) + goto cleanup; + + if (priv->preMigrationState == VIR_DOMAIN_RUNNING) { /* This is basically the only restore possibility that's safe * and we should attempt to do */ @@ -2365,6 +2372,46 @@ qemuMigrationSetOffline(virQEMUDriverPtr driver, } +void +qemuMigrationPostcopyFailed(virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + virObjectEventPtr event; + virDomainState state; + int reason; + + state = virDomainObjGetState(vm, &reason); + + if (state != VIR_DOMAIN_PAUSED && + state != VIR_DOMAIN_RUNNING) + return; + + if (state == VIR_DOMAIN_PAUSED && + reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED) + return; + + VIR_WARN("Migration of domain %s failed during post-copy; " + "leaving the domain paused", vm->def->name); + + if (state == VIR_DOMAIN_RUNNING) { + if (qemuProcessStopCPUs(driver, vm, + VIR_DOMAIN_PAUSED_POSTCOPY_FAILED, + QEMU_ASYNC_JOB_MIGRATION_IN) < 0) { + VIR_WARN("Unable to pause guest CPUs for %s", vm->def->name); + return; + } + } else { + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_POSTCOPY_FAILED); + } + + event = virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY_FAILED); + qemuDomainEventQueue(driver, event); +} + + static int qemuMigrationSetCompression(virQEMUDriverPtr driver, virDomainObjPtr vm, @@ -4078,8 +4125,8 @@ qemuMigrationConfirmPhase(virQEMUDriverPtr driver, if (flags & VIR_MIGRATE_OFFLINE) goto done; - /* Did the migration go as planned? If yes, kill off the - * domain object, but if no, resume CPUs + /* Did the migration go as planned? If yes, kill off the domain object. + * If something failed, resume CPUs, but only if we didn't use post-copy. */ if (retcode == 0) { /* If guest uses SPICE and supports seamless migration we have to hold @@ -4095,6 +4142,7 @@ qemuMigrationConfirmPhase(virQEMUDriverPtr driver, VIR_DOMAIN_EVENT_STOPPED_MIGRATED); } else { virErrorPtr orig_err = virSaveLastError(); + int reason; /* cancel any outstanding NBD jobs */ qemuMigrationCancelDriveMirror(driver, vm, false, @@ -4103,7 +4151,10 @@ qemuMigrationConfirmPhase(virQEMUDriverPtr driver, virSetError(orig_err); virFreeError(orig_err); - if (qemuMigrationRestoreDomainState(conn, vm)) { + if (virDomainObjGetState(vm, &reason) == VIR_DOMAIN_PAUSED && + reason == VIR_DOMAIN_PAUSED_POSTCOPY) { + qemuMigrationPostcopyFailed(driver, vm); + } else if (qemuMigrationRestoreDomainState(conn, vm)) { event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_MIGRATED); @@ -5911,6 +5962,7 @@ qemuMigrationFinish(virQEMUDriverPtr driver, unsigned long long timeReceived = 0; virObjectEventPtr event; bool inPostCopy = false; + bool kill = true; VIR_DEBUG("driver=%p, dconn=%p, vm=%p, cookiein=%s, cookieinlen=%d, " "cookieout=%p, cookieoutlen=%p, flags=%lx, retcode=%d", @@ -6044,6 +6096,7 @@ qemuMigrationFinish(virQEMUDriverPtr driver, } if (inPostCopy) { + kill = false; event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_POSTCOPY); @@ -6104,13 +6157,17 @@ qemuMigrationFinish(virQEMUDriverPtr driver, if (!dom && !(flags & VIR_MIGRATE_OFFLINE) && virDomainObjIsActive(vm)) { - qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, - VIR_QEMU_PROCESS_STOP_MIGRATED); - virDomainAuditStop(vm, "failed"); - event = virDomainEventLifecycleNewFromObj(vm, - VIR_DOMAIN_EVENT_STOPPED, - VIR_DOMAIN_EVENT_STOPPED_FAILED); - qemuDomainEventQueue(driver, event); + if (kill) { + qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, + VIR_QEMU_PROCESS_STOP_MIGRATED); + virDomainAuditStop(vm, "failed"); + event = virDomainEventLifecycleNewFromObj(vm, + VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FAILED); + qemuDomainEventQueue(driver, event); + } else { + qemuMigrationPostcopyFailed(driver, vm); + } } if (dom && diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index e30b2ec..46b37a3 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -211,4 +211,7 @@ int qemuMigrationRunIncoming(virQEMUDriverPtr driver, const char *uri, qemuDomainAsyncJob asyncJob); +void qemuMigrationPostcopyFailed(virQEMUDriverPtr driver, + virDomainObjPtr vm); + #endif /* __QEMU_MIGRATION_H__ */ diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index a0c93c9..fba9a65 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -3270,8 +3270,13 @@ qemuProcessRecoverMigrationIn(virQEMUDriverPtr driver, virConnectPtr conn, qemuMigrationJobPhase phase, virDomainState state, - int reason ATTRIBUTE_UNUSED) + int reason) { + bool postcopy = (state == VIR_DOMAIN_PAUSED && + reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED) || + (state == VIR_DOMAIN_RUNNING && + reason == VIR_DOMAIN_RUNNING_POSTCOPY); + switch (phase) { case QEMU_MIGRATION_PHASE_NONE: case QEMU_MIGRATION_PHASE_PERFORM2: @@ -3304,8 +3309,10 @@ qemuProcessRecoverMigrationIn(virQEMUDriverPtr driver, case QEMU_MIGRATION_PHASE_FINISH3: /* migration finished, we started resuming the domain but didn't * confirm success or failure yet; killing it seems safest unless - * we already started guest CPUs */ - if (state != VIR_DOMAIN_RUNNING) { + * we already started guest CPUs or we were in post-copy mode */ + if (postcopy) { + qemuMigrationPostcopyFailed(driver, vm); + } else if (state != VIR_DOMAIN_RUNNING) { VIR_DEBUG("Killing migrated domain %s", vm->def->name); return -1; } @@ -3323,6 +3330,10 @@ qemuProcessRecoverMigrationOut(virQEMUDriverPtr driver, virDomainState state, int reason) { + bool postcopy = state == VIR_DOMAIN_PAUSED && + (reason == VIR_DOMAIN_PAUSED_POSTCOPY || + reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED); + switch (phase) { case QEMU_MIGRATION_PHASE_NONE: case QEMU_MIGRATION_PHASE_PREPARE: @@ -3340,26 +3351,44 @@ qemuProcessRecoverMigrationOut(virQEMUDriverPtr driver, 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("Cancelling unfinished migration of domain %s", - vm->def->name); - if (qemuMigrationCancel(driver, vm) < 0) { - VIR_WARN("Could not cancel ongoing migration of domain %s", - vm->def->name); + * domain; however we can only do that before migration enters + * post-copy mode + */ + if (postcopy) { + qemuMigrationPostcopyFailed(driver, vm); + } else { + VIR_DEBUG("Cancelling unfinished migration of domain %s", + vm->def->name); + if (qemuMigrationCancel(driver, vm) < 0) { + VIR_WARN("Could not cancel ongoing migration of domain %s", + vm->def->name); + } + goto resume; } - goto resume; + 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 + * of Finish3 step; third party needs to check what to do next; in + * post-copy mode we can use PAUSED_POSTCOPY_FAILED state for this */ + if (postcopy) + qemuMigrationPostcopyFailed(driver, vm); 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); - goto resume; + /* Finish3 failed, we need to resume the domain, but once we enter + * post-copy mode there's no way back, so let's just mark the domain + * as broken in that case + */ + if (postcopy) { + qemuMigrationPostcopyFailed(driver, vm); + } else { + VIR_DEBUG("Resuming domain %s after failed migration", + vm->def->name); + goto resume; + } + break; case QEMU_MIGRATION_PHASE_CONFIRM3: /* migration completed, we need to kill the domain here */ -- 2.7.0

In post-copy mode none of the hosts has a complete guest state and rolling back migration is impossible. Thus aborting it would be equivalent to destroying the domain. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - no change src/qemu/qemu_driver.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index c62561b..341c731 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -13138,6 +13138,7 @@ static int qemuDomainAbortJob(virDomainPtr dom) virDomainObjPtr vm; int ret = -1; qemuDomainObjPrivatePtr priv; + int reason; if (!(vm = qemuDomObjFromDomain(dom))) goto cleanup; @@ -13160,13 +13161,24 @@ static int qemuDomainAbortJob(virDomainPtr dom) virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("no job is active on the domain")); goto endjob; - } else if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_IN) { + } + + if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_IN) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot abort incoming migration;" " use virDomainDestroy instead")); goto endjob; } + if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT && + (priv->job.current->stats.status == QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY || + (virDomainObjGetState(vm, &reason) == VIR_DOMAIN_PAUSED && + reason == VIR_DOMAIN_PAUSED_POSTCOPY))) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("cannot abort migration in post-copy mode")); + goto endjob; + } + VIR_DEBUG("Cancelling job at client request"); qemuDomainObjAbortAsyncJob(vm); qemuDomainObjEnterMonitor(driver, vm); -- 2.7.0

The function already takes two bool arguments, switching to flags makes it a lot easier to read. Especially in case we need to add another boolean in the future. Signed-off-by: Jiri Denemark <jdenemar@redhat.com> --- Notes: Version 2: - moved to the end of the series since it is not required by another patch in this series anymore src/qemu/qemu_migration.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 2a34b4a..55a8be0 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -2847,21 +2847,14 @@ qemuMigrationWaitForCompletion(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob, virConnectPtr dconn, - bool abort_on_error, - bool storage) + unsigned int flags) { qemuDomainObjPrivatePtr priv = vm->privateData; qemuDomainJobInfoPtr jobInfo = priv->job.current; bool events = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT); - unsigned int flags; int rv; - flags = QEMU_MIGRATION_COMPLETED_UPDATE_STATS | - QEMU_MIGRATION_COMPLETED_POSTCOPY; - if (abort_on_error) - flags |= QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR; - if (storage) - flags |= QEMU_MIGRATION_COMPLETED_CHECK_STORAGE; + flags |= QEMU_MIGRATION_COMPLETED_UPDATE_STATS; jobInfo->type = VIR_DOMAIN_JOB_UNBOUNDED; while ((rv = qemuMigrationCompleted(driver, vm, asyncJob, @@ -4527,6 +4520,7 @@ qemuMigrationRun(virQEMUDriverPtr driver, bool abort_on_error = !!(flags & VIR_MIGRATE_ABORT_ON_ERROR); bool events = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT); bool inPostCopy = false; + unsigned int waitFlags; int rc; VIR_DEBUG("driver=%p, vm=%p, cookiein=%s, cookieinlen=%d, " @@ -4709,9 +4703,17 @@ qemuMigrationRun(virQEMUDriverPtr driver, fd = -1; } + waitFlags = 0; + if (abort_on_error) + waitFlags |= QEMU_MIGRATION_COMPLETED_ABORT_ON_ERROR; + if (mig->nbd) + waitFlags |= QEMU_MIGRATION_COMPLETED_CHECK_STORAGE; + if (flags & VIR_MIGRATE_POSTCOPY) + waitFlags |= QEMU_MIGRATION_COMPLETED_POSTCOPY; + rc = qemuMigrationWaitForCompletion(driver, vm, QEMU_ASYNC_JOB_MIGRATION_OUT, - dconn, abort_on_error, !!mig->nbd); + dconn, waitFlags); if (rc == -2) goto cancel; else if (rc == -1) @@ -6327,8 +6329,7 @@ qemuMigrationToFile(virQEMUDriverPtr driver, virDomainObjPtr vm, if (rc < 0) goto cleanup; - rc = qemuMigrationWaitForCompletion(driver, vm, asyncJob, - NULL, false, false); + rc = qemuMigrationWaitForCompletion(driver, vm, asyncJob, NULL, 0); if (rc < 0) { if (rc == -2) { -- 2.7.0

I forgot to mention that all patches are also available in post-copy-migration branch in my staging repository at https://gitlab.com/jirkade/libvirt Jirka
participants (2)
-
Daniel P. Berrange
-
Jiri Denemark